Saltar al contenido principal

Decision note: codegen instead of runtime reflection

TsVRC resolves a project's own concrete types (which class is your Instance subclass, which behaviours are registered as Constructs, which prefabs are pooled) once, at edit time, and bakes the result into generated C# with direct field references and typed accessors. A framework built the same way in ordinary C# would more commonly reach for runtime reflection or a dependency-injection container to do this lookup dynamically, lazily, whenever it's actually needed. TsVRC doesn't have that option, for a concrete, checkable reason: the runtime environment it targets doesn't support it.

What Udon-compiled code can't do

UdonSharp compiles against a fixed set of APIs the Udon VM exposes ("externs"), determined by walking the .NET type system at compile time, not at the compiled program's own runtime. VRChat's own documentation of the Udon VM states this directly: "reflection is not used to access functions," though the VM is "'styled' like it is," meaning a function call compiles down to a fixed extern name resolved ahead of time, not a live, System.Reflection-style lookup by member name at runtime. (The reflection calls you'll find inside UdonSharp's own tooling, for example the Editor-side "proxy" object every UdonSharpBehaviour has, run in the Unity Editor's own C# context while compiling or inspecting a script; they never execute as part of the compiled Udon bytecode itself.)

Generics compound the problem: UdonSharp's own FAQ lists generic classes and generic methods among the C# features that don't work in Udon. A typical reflection-or-DI-based framework leans on both together: reflect over a type, then resolve or construct it generically. With neither tool available in compiled Udon code, that whole approach is closed off, not just inconvenient.

Object creation is restricted the same way: the only instantiation method VRChat's own API reference lists is GameObject VRCInstantiate(GameObject original), a single, non-generic overload. Since generic methods don't work in Udon at all (the same restriction cited above), there's no way a generic Instantiate<T>() returning an arbitrary, runtime-determined component type could exist here either, and reading a component back off a freshly instantiated object has the same limitation. That's part of why generated factory code (see FactoryModule) resolves its type at generation time and emits a concrete, non-generic GetComponent<ConcreteType>() call rather than anything parameterized.

What TsVRC does instead

None of that machinery exists in compiled Udon, but all of it is perfectly ordinary, supported C# in the Unity Editor, where TsVRC's own reflection happens. Scanning loaded assemblies for an Instance subclass, or a class tagged [TsWorldExtensionPoint], runs entirely in the editor's own C# runtime, at generate time, never inside a compiled UdonSharpBehaviour. The generator does the type resolution once, while it still has full reflection and generics available to it, and writes the result (a concrete field, a concrete property, a concrete method signature) as plain generated code that the Udon compiler then compiles like any other UdonSharp script. By the time a world is actually running, there's no lookup left to do at all: _ts.Name is a real, direct field access, not a dictionary lookup or a reflected member access, because the framework already did that resolution before the world was ever built.