Saltar al contenido principal

TsSingleComponentModule

Tsvrc.Editor.TsSingleComponentModule is the shared abstract base behind LogModule and MemoryModule. Both generate exactly one [ReadOnly][SerializeField] field, backed by exactly one library-internal singleton component that the module itself creates and heals in the scene. Rather than duplicating identical GenerateCode/AfterFilesStable/Wire/OnSceneHierarchyChanged logic twice, this class implements all of it once, parameterized by seven abstract members a subclass supplies: ComponentType, FieldName, PublicPropertyName, StartMethodName, ChildGameObjectName, ScriptPath, AssetPath, and ModuleTag.

Usage-gated, not existence-gated

TsRoot.Log/TsRoot.Memory are declared virtual ... => null, not abstract, so a project that never uses logging or memory compiles perfectly fine without either field ever being generated at all. Usage (tracked via the shared ApplyTreeShaking mechanism, treating this single subsystem as a one-element list) gates exactly two things: whether GenerateCode() emits the real field/property/start-method or a stub, and whether the backing scene child object exists at all. A caller reading _ts.Log or _ts.Memory while genuinely unused simply gets TsRoot's own null default. There's no special-casing needed elsewhere in the framework for "this subsystem happens to be turned off."

IsUsed is exposed as a protected property specifically so LogModule's own Settings-tab integration can tell "never generated yet, click Force Regenerate" apart from "deliberately tree-shaken away, won't reappear until referenced or force-included." Without it, the tab's guidance would be actively misleading once tree-shaking legitimately turns this subsystem off.

Self-healing, but never destructive to the shared package asset

AfterFilesStable() always calls ScaffoldModule.EnsureUdonSharpProgramAsset for ComponentType's own script/asset pair, regardless of current usage. Those are permanent package resources (TsvrcLogger.cs/.asset, TsvrcMemory.cs/.asset), never created or deleted by this module, only healed if something about their linkage drifted. What usage does control is the scene-local child object: when unused, an existing child under the scaffold root is torn down (mirroring how FactoryModule empties its own container); when used, ScaffoldModule.EnsureChildSceneObject creates or repairs it.

Wire() skips entirely while unused. There's no field on the compiled type to wire in that state (GenerateCode() omitted it), and attempting to find it would trigger the shared "force compile to regenerate" warning every single pass for a field that's deliberately, permanently absent rather than merely stale.