- Package:
com.gamelovers.services - Unity: 6000.0+
- Dependencies (see
package.json)com.gamelovers.gamedata(1.0.0) (containsfloatP, used byRngService)
This package provides a set of small, modular “foundation services” for Unity projects (service locator/DI-lite, messaging, ticking, coroutines, pooling, persistence, RNG, time, and build version helpers).
For user-facing docs, treat README.md as the primary entry point. This file is for contributors/agents working on the package itself.
- Service locator / bindings:
Runtime/Installer.cs,Runtime/MainInstaller.csInstallerstores aDictionary<Type, object>of interface type → instance.MainInstalleris a static wrapper over a singleInstallerinstance (global scope).- Binding is instance-based (
Bind<T>(T instance)), not “type-to-type” or lifetime-managed DI. - Only interfaces can be bound (binding a non-interface throws).
- Messaging:
Runtime/MessageBrokerService.cs- Message contract:
IMessage - Pub/sub via
IMessageBrokerService(Publish,PublishSafe,Subscribe,Unsubscribe,UnsubscribeAll) - Stores subscribers keyed by
action.Target(so static method subscriptions are not supported).
- Message contract:
- Tick / update fan-out:
Runtime/TickService.cs- Creates a
DontDestroyOnLoadGameObject withTickServiceMonoBehaviourto drive Update/LateUpdate/FixedUpdate callbacks. - Supports “buffered” ticking (
deltaTime) and optional overflow carry (timeOverflowToNextTick) to reduce drift. - Uses scaled (
Time.time) or unscaled (Time.realtimeSinceStartup) time depending onrealTime.
- Creates a
- Coroutine host:
Runtime/CoroutineService.cs- Creates a
DontDestroyOnLoadGameObject withCoroutineServiceMonoBehaviourto run coroutines from pure C# code. IAsyncCoroutine/IAsyncCoroutine<T>wraps a UnityCoroutineand offers completion callbacks + state flags.
- Creates a
- Pooling:
- Pool registry:
Runtime/PoolService.cs(PoolService : IPoolService) - Pool implementations:
Runtime/ObjectPool.cs- Generic
ObjectPool<T> - Unity-specific:
GameObjectPool,GameObjectPool<TBehaviour>
- Generic
- Lifecycle hooks:
IPoolEntitySpawn,IPoolEntitySpawn<TData>,IPoolEntityDespawn,IPoolEntityObject<T>
- Pool registry:
- Persistence:
Runtime/DataService.cs- In-memory store keyed by
Type - Disk persistence via
PlayerPrefs+Newtonsoft.Jsonserialization
- In-memory store keyed by
- Time + manipulation:
Runtime/TimeService.csITimeService+ITimeManipulatorfor querying time (Unity / Unix / DateTime UTC) and applying offsets.
- Deterministic RNG:
Runtime/RngService.cs- Deterministic RNG state stored in
RngDataand exposed viaIRngData. - Float API uses
floatP(fromcom.gamelovers.dataextensions) for deterministic float math.
- Deterministic RNG state stored in
- Build/version info:
Runtime/VersionServices.cs- Runtime access to version strings and git/build metadata loaded from a Resources TextAsset.
- Runtime:
Runtime/- Entry points:
MainInstaller.cs,Installer.cs - Services:
MessageBrokerService.cs,TickService.cs,CoroutineService.cs,PoolService.cs,DataService.cs,TimeService.cs,RngService.cs,VersionServices.cs - Pooling:
ObjectPool.cs
- Entry points:
- Editor:
Editor/- Version data generation:
VersionEditorUtils.cs,GitEditorProcess.cs - Must remain editor-only (relies on
UnityEditor+ starting git processes)
- Version data generation:
- Tests:
Tests/- EditMode/PlayMode tests validating service behavior
MainInstallerAPI vs README snippetsMainInstalleris a static class exposingBind/Resolve/TryResolve/Clean.- If you see docs/examples referring to
MainInstaller.Instanceor fluent bindings, verify against runtime code—those snippets may be stale.
- Message broker mutation safety
Publish<T>iterates subscribers directly; subscribing/unsubscribing during publish is blocked and throws.- Use
PublishSafe<T>if you have chain subscriptions/unsubscriptions during message handling (it copies delegates first, at extra allocation cost). Subscribeusesaction.Targetas the subscriber key, so static methods cannot subscribe.
- Tick/coroutine services allocate a global GameObject
TickServiceandCoroutineServiceeach create aDontDestroyOnLoadGameObject. CallDispose()when you want to tear them down (tests, game reset, domain reload edge cases).- These services do not enforce a singleton at runtime; constructing multiple instances will create multiple host GameObjects.
IAsyncCoroutine.StopCoroutine(triggerOnComplete)- The current implementation triggers completion callbacks even when
triggerOnCompleteisfalse(parameter is not respected). Keep this in mind if you rely on cancellation semantics.
- The current implementation triggers completion callbacks even when
- DataService persistence details
- Keys are
typeof(T).NameinPlayerPrefs(name collisions are possible across assemblies/types with same name). LoadData<T>requiresTto have a parameterless constructor (viaActivator.CreateInstance<T>()) if no data exists.
- Keys are
- Pool lifecycle
PoolServicekeeps one pool per type; it does not guard against duplicateAddPool<T>()calls (duplicate adds throw fromDictionary.Add).GameObjectPool.Dispose(bool)destroys theSampleEntityGameObject;GameObjectPool.Dispose()destroys pooled instances but does not necessarily destroy the sample reference—be explicit about disposal expectations when changing pool behavior.
GameObjectPoolandGameObjectPool<T>overrideCallOnSpawned/CallOnDespawned(virtual methods) to useGetComponent<IPoolEntitySpawn>()/GetComponent<IPoolEntityDespawn>()for lifecycle hooks on components. This differs fromObjectPool<T>which casts the entity directly.- Version data pipeline
- Runtime expects a Resources TextAsset named
version-data(VersionServices.VersionDataFilename). VersionEditorUtilswritesAssets/Configs/Resources/version-data.txton editor load and can be invoked before builds. It uses git CLI; failures should be handled gracefully.- Accessors like
VersionServices.VersionInternalwill throw if version data hasn’t been loaded yet—callVersionServices.LoadVersionDataAsync()early (and decide how you want to handle load failures).
- Runtime expects a Resources TextAsset named
- C#: C# 9.0 syntax; explicit namespaces; no global usings.
- Assemblies
- Runtime must not reference
UnityEditor. - Editor tooling must live under
Editor/(or be guarded with#if UNITY_EDITORif absolutely necessary).
- Runtime must not reference
- Performance
- Be mindful of allocations in hot paths (e.g.,
PublishSafeallocates; tick lists mutate; avoid per-frame allocations).
- Be mindful of allocations in hot paths (e.g.,
Prefer local UPM cache / local packages when needed:
- GameData:
Packages/com.gamelovers.gamedata/(e.g.,floatP) - Unity Newtonsoft JSON (Unity package): check
Library/PackageCache/if you need source details
- Add a new service
- Add runtime interface + implementation under
Runtime/(keep UnityEngine usage minimal if possible). - Add/adjust tests under
Tests/. - If the service needs Unity callbacks, follow the
TickService/CoroutineServicepattern (singleDontDestroyOnLoadhost object +Dispose()).
- Add runtime interface + implementation under
- Bind/resolve services
- Bind instances via
MainInstaller.Bind<IMyService>(myServiceInstance). - Resolve via
MainInstaller.Resolve<IMyService>()orTryResolve. - Clear bindings on reset via
MainInstaller.Clean()(orClean<T>()/CleanDispose<T>()).
- Bind instances via
- Update versioning
- Ensure
version-data.txtexists/updates correctly inAssets/Configs/Resources/. - If changing
VersionServices.VersionData, update both runtime parsing andVersionEditorUtilswriting logic.
- Ensure
Update this file when:
- The binding/service-locator API changes (
Installer,MainInstaller) - Core service behavior changes (publish safety rules, tick timing, coroutine completion/cancellation semantics, pooling lifecycle)
- Versioning pipeline changes (resource filename, editor generator behavior, runtime parsing)
- Dependencies change (
package.json, new external types likefloatP)