1"use strict";(self.webpackChunktsvrc_docs=self.webpackChunktsvrc_docs||[]).push([["281"],{7015(e,r,n){n.r(r),n.d(r,{metadata:()=>a,default:()=>h,frontMatter:()=>i,contentTitle:()=>o,toc:()=>l,assets:()=>c});var a=JSON.parse('{"id":"tsvrc/players-tracking/auto-player-tracker","title":"AutoPlayerTracker","description":"Tsvrc.Tracking.AutoPlayerTracker is a PlayerTracker that tracks","source":"@site/docs/tsvrc/players-tracking/auto-player-tracker.md","sourceDirName":"tsvrc/players-tracking","slug":"/tsvrc/players-tracking/auto-player-tracker","permalink":"/es/docs/tsvrc/players-tracking/auto-player-tracker","draft":false,"unlisted":false,"editUrl":"https://github.com/tsvrc/docs/tree/main/docs/tsvrc/players-tracking/auto-player-tracker.md","tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"id":"auto-player-tracker","title":"AutoPlayerTracker","sidebar_position":3},"sidebar":"tutorialSidebar","previous":{"title":"PlayerTracker","permalink":"/es/docs/tsvrc/players-tracking/player-tracker"},"next":{"title":"ReadyCheckProcess","permalink":"/es/docs/tsvrc/players-tracking/ready-check-process"}}'),t=n(4848),s=n(8453);let i={id:"auto-player-tracker",title:"AutoPlayerTracker",sidebar_position:3},o="AutoPlayerTracker",c={},l=[{value:"Getting an instance",id:"getting-an-instance",level:2},{value:"Usage",id:"usage",level:2},{value:"The initial-snapshot timing",id:"the-initial-snapshot-timing",level:2},{value:"Events",id:"events",level:2}];function d(e){let r={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(r.header,{children:(0,t.jsx)(r.h1,{id:"autoplayertracker",children:"AutoPlayerTracker"})}),"\n",(0,t.jsxs)(r.p,{children:[(0,t.jsx)(r.code,{children:"Tsvrc.Tracking.AutoPlayerTracker"})," is a ",(0,t.jsx)(r.a,{href:"./player-tracker",children:"PlayerTracker"})," that tracks\nevery player in the instance automatically: no explicit player list to manage. Its\ngenerated shadow is ",(0,t.jsx)(r.code,{children:"TsAutoPlayerTracker"}),". See ",(0,t.jsx)(r.a,{href:"../how-to/tracking-a-group-of-players",children:"Track a custom group of\nplayers"})," for when to reach for this over a bare\n",(0,t.jsx)(r.code,{children:"PlayerTracker"}),", or ",(0,t.jsx)(r.a,{href:"../how-to/wiring-a-ready-check",children:"Gate something on all players being\nready"})," for a common pairing with ",(0,t.jsx)(r.code,{children:"ReadyCheckProcess"}),"."]}),"\n",(0,t.jsx)(r.h2,{id:"getting-an-instance",children:"Getting an instance"}),"\n",(0,t.jsxs)(r.p,{children:[(0,t.jsx)(r.code,{children:"AutoPlayerTracker"})," ships as one of ",(0,t.jsx)(r.a,{href:"../explanations/builtin-registrations",children:"TsVRC's own default pool\nentries"})," (",(0,t.jsx)(r.code,{children:"TsBuiltinConfig"}),")."]}),"\n",(0,t.jsxs)(r.ul,{children:["\n",(0,t.jsxs)(r.li,{children:[(0,t.jsx)(r.strong,{children:"Default:"})," a ",(0,t.jsx)(r.code,{children:"[WirePool]"})," field of this type just resolves, nothing to register in\nConfigure first, and codegen calls ",(0,t.jsx)(r.code,{children:"TsConstruct"})," for you at wire time."]}),"\n",(0,t.jsxs)(r.li,{children:[(0,t.jsx)(r.strong,{children:"Manual:"})," skip pooling entirely. Drag the shipped ",(0,t.jsx)(r.code,{children:"AutoPlayerTracker"})," prefab into your\nscene and call ",(0,t.jsx)(r.code,{children:"TsConstruct"})," on it yourself before calling anything else on it. Skipping\nconstruction leaves ",(0,t.jsxs)(r.a,{href:"../core-concepts/process",children:[(0,t.jsx)(r.code,{children:"Process"}),"'s cached local-player ID"]})," at\nits default, which makes ",(0,t.jsx)(r.code,{children:"IsProcessOwner()"})," report ",(0,t.jsx)(r.code,{children:"false"})," even for the actual owner."]}
1),"\n"]}),"\n",(0,t.jsx)(r.h2,{id:"usage",children:"Usage"}),"\n",(0,t.jsxs)(r.p,{children:["Call ",(0,t.jsx)(r.code,{children:"StartAutoTracking()"})," instead of ",(0,t.jsx)(r.code,{children:"StartPlayerTracking"}),". The base method is\noverridden to redirect to the automatic snapshot regardless of what's passed to it, and\nboth of its parameters (",(0,t.jsx)(r.code,{children:"playerIds"}),", ",(0,t.jsx)(r.code,{children:"useProcessUpdate"}),") are ignored entirely;\n",(0,t.jsx)(r.code,{children:"AutoPlayerTracker"})," never uses the process-update loop.\n",(0,t.jsx)(r.code,{children:"StopAutoTracking()"}),"/",(0,t.jsx)(r.code,{children:"CompleteAutoTracking()"})," are thin renames of the base\n",(0,t.jsx)(r.code,{children:"StopPlayerTracking"}),"/",(0,t.jsx)(r.code,{children:"CompletePlayerTracking"}),"."]}),"\n",(0,t.jsxs)(r.p,{children:["From there, it's genuinely automatic: on the process owner, every ",(0,t.jsx)(r.code,{children:"OnPlayerJoined"})," while\nthe process is running adds that player. VRChat player references ",(0,t.jsx)(r.a,{href:"https://creators.vrchat.com/worlds/udon/players/#isvalid",children:"can be\ninvalid"}),", and accessing one\nthat is has been reported to throw and crash the behaviour, so the join is skipped\nwhenever the reference fails that check. Every departure or suspension is removed\nthrough the same paths ",(0,t.jsx)(r.code,{children:"PlayerTracker"})," already provides. Non-owners don't act on\n",(0,t.jsx)(r.code,{children:"OnPlayerJoined"})," at all: there's no local add to forward, since only the owner's copy of\nthe tracked set is authoritative."]}),"\n",(0,t.jsx)(r.pre,{children:(0,t.jsx)(r.code,{className:"language-csharp",children:'public class InstanceRoster : TsBehaviour\n{\n [WirePool][SerializeField] private AutoPlayerTracker _tracker;\n\n protected override void TsStart()\n {\n _tracker.TsSubscribe(this, AutoPlayerTracker.OnAutoTrackingPlayersAddedEvent, nameof(_OnRosterChanged));\n _tracker.TsSubscribe(this, AutoPlayerTracker.OnAutoTrackingPlayersRemovedEvent, nameof(_OnRosterChanged));\n _tracker.StartAutoTracking();\n }\n\n public void _OnRosterChanged() => LogInfo(_tracker.LastPlayerIds.Length + " players in the instance.");\n}\n'})}),"\n",(0,t.jsxs)(r.p,{children:["No add/remove calls of your own: join and leave are handled entirely by the base\n",(0,t.jsx)(r.code,{children:"PlayerTracker"})," machinery once ",(0,t.jsx)(r.code,{children:"StartAutoTracking()"})," has run."]}),"\n",(0,t.jsx)(r.h2,{id:"the-initial-snapshot-timing",children:"The initial-snapshot timing"}),"\n",(0,t.jsxs)(r.p,{children:["The player list ",(0,t.jsx)(r.code,{children:"StartAutoTracking"})," captures is read at the moment ",(0,t.jsx)(r.code,{children:"StartAutoTracking"}),"\nis called, not inside ",(0,t.jsx)(r.code,{children:"OnPlayerJoined"}),". This matters because of how VRChat handles the\nlocal player's own join: ",(0,t.jsxs)(r.a,{href:"https://creators.vrchat.com/worlds/udon/graph/event-nodes/#onplayerjoined",children:["joining an instance fires ",(0,t.jsx)(r.code,{children:"OnPlayerJoined"})," for every player\nalready present, including\nyourself"]}),".\nThat entire wave fires before any of your own code has run, so ",(0,t.jsx)(r.code,{children:"IsProcessRunning()"})," is\nstill ",(0,t.jsx)(r.code,{children:"false"})," for all of it, and every one of those events is dropped. By the time\n",(0,t.jsx)(r.code,{children:"StartAutoTracking"})," actually runs, that wave is long gone; reading the live player list\nat that instant is what captures everyone who's genuinely present. A player who is\nstill mid-join at that exact instant hasn't fired their own ",(0,t.jsx)(r.code,{children:"OnPlayerJoined"})," yet. It\nfires afterward, once the process is live, and gets picked up normally through the\nordinary join path. Because UdonSharp runs on Unity's single main thread, no\n",(0,t.jsx)(r.code,{children:"OnPlayerJoined"})," can interleave between reading that snapshot and the process actually\ngoing live, so there's no window where a joining player is missed by both paths."]}),"\n",(0,t.jsxs)(r.p,{children:["The snapshot itself excludes any player already suspended at that instant. A player\nwho's suspended before tracking even starts has no suspend ",(0,t.jsx)(r.em,{children:"transition"})," left to\nproduce, and ",(0,t.jsx)(r.code,{children:"PlayerTracker"}),"'s own suspend-handling only reacts to that transition, so a\npre-suspended player must never enter the tracked set in the first place."]}),"\n",(0,t.jsx)(r.h2,{id:"events",children:"Events"}),"\n",(0,t.jsxs)(r.p,{children:["Same six lifecycle names as ",(0,t.jsx)(r.code,{children:"PlayerTracker"}),", renamed with an ",(0,t.jsx)(r.code,{children:"Auto"})," prefix\n(",(0,t.jsx)(r.code,{children:"OnAutoTrackingStarted"}),", ",(0,t.jsx)(r.code,{children:"...Stopped"}),", ",(0,t.jsx)(r.code,{children:"...Completed"}),", ",(0,t.jsx)(r.code,{children:"...Deserialization"}),",\n",(0,t.jsx)(r.code,{children:"...PlayersAdded"}),", ",(0,t.jsx)(r.code,{children:"...PlayersRemoved"}),") and their matching ",(0,t.jsx)(r.code,{children:"OnAutoTracking*Event"}),"\nconstants. Each is just the base ",(0,t.jsx)(r.code,{children:"OnTracking*"})," hook overridden to emit the renamed\nevent. The underlying data (",(0,t.jsx)(r.code,{children:"LastPlayerIds"}),", ",(0,t.jsx)(r.code,{children:"LastAddedPlayerIds"}),",\n",(0,t.jsx)(r.code,{children:"LastRemovedPlayerIds"}),") is identical to ",(0,t.jsx)(r.code,{children:"PlayerTracker"}),"'s own."]})]})}function h(e={}){let{wrapper:r}={...(0,s.R)(),...e.components};return r?(0,t.jsx)(r,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453(e,r,n){n.d(r,{R:()=>i,x:()=>o});var a=n(6540);let t={},s=a.createContext(t);function i(e){let r=a.useContext(s);return a.useMemo(function(){return"function"==typeof e?e(r):{...r,...e}},[r,e])}function o(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:i(e.components),a.createElement(s.Provider,{value:r},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.