Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# GameLovers.Services - AI Agent Guide

## 1. Package Overview
- **Package**: `com.gamelovers.services`
- **Unity**: 6000.0+
- **Dependencies** (see `package.json`)
- `com.gamelovers.dataextensions` (**0.6.2**) (contains `floatP`, used by `RngService`)

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.

## 2. Runtime Architecture (high level)
- **Service locator / bindings**: `Runtime/Installer.cs`, `Runtime/MainInstaller.cs`
- `Installer` stores a `Dictionary<Type, object>` of interface type → instance.
- `MainInstaller` is a **static** wrapper over a single `Installer` instance (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**).
- **Tick / update fan-out**: `Runtime/TickService.cs`
- Creates a `DontDestroyOnLoad` GameObject with `TickServiceMonoBehaviour` to 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 on `realTime`.
- **Coroutine host**: `Runtime/CoroutineService.cs`
- Creates a `DontDestroyOnLoad` GameObject with `CoroutineServiceMonoBehaviour` to run coroutines from pure C# code.
- `IAsyncCoroutine` / `IAsyncCoroutine<T>` wraps a Unity `Coroutine` and offers completion callbacks + state flags.
- **Pooling**:
- Pool registry: `Runtime/PoolService.cs` (`PoolService : IPoolService`)
- Pool implementations: `Runtime/ObjectPool.cs`
- Generic `ObjectPool<T>`
- Unity-specific: `GameObjectPool`, `GameObjectPool<TBehaviour>`
- Lifecycle hooks: `IPoolEntitySpawn`, `IPoolEntitySpawn<TData>`, `IPoolEntityDespawn`, `IPoolEntityObject<T>`
- **Persistence**: `Runtime/DataService.cs`
- In-memory store keyed by `Type`
- Disk persistence via `PlayerPrefs` + `Newtonsoft.Json` serialization
- **Time + manipulation**: `Runtime/TimeService.cs`
- `ITimeService` + `ITimeManipulator` for querying time (Unity / Unix / DateTime UTC) and applying offsets.
- **Deterministic RNG**: `Runtime/RngService.cs`
- Deterministic RNG state stored in `RngData` and exposed via `IRngData`.
- Float API uses `floatP` (from `com.gamelovers.dataextensions`) for deterministic float math.
- **Build/version info**: `Runtime/VersionServices.cs`
- Runtime access to version strings and git/build metadata loaded from a Resources TextAsset.

## 3. Key Directories / Files
- **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`
- **Editor**: `Editor/`
- Version data generation: `VersionEditorUtils.cs`, `GitEditorProcess.cs`
- Must remain editor-only (relies on `UnityEditor` + starting git processes)
- **Tests**: `Tests/`
- EditMode/PlayMode tests validating service behavior

## 4. Important Behaviors / Gotchas
- **`MainInstaller` API vs README snippets**
- `MainInstaller` is a static class exposing `Bind/Resolve/TryResolve/Clean`.
- If you see docs/examples referring to `MainInstaller.Instance` or 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).
- `Subscribe` uses `action.Target` as the subscriber key, so **static methods cannot subscribe**.
- **Tick/coroutine services allocate a global GameObject**
- `TickService` and `CoroutineService` each create a `DontDestroyOnLoad` GameObject. Call `Dispose()` 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 `triggerOnComplete` is `false` (parameter is not respected). Keep this in mind if you rely on cancellation semantics.
- **DataService persistence details**
- Keys are `typeof(T).Name` in `PlayerPrefs` (name collisions are possible across assemblies/types with same name).
- `LoadData<T>` requires `T` to have a parameterless constructor (via `Activator.CreateInstance<T>()`) if no data exists.
- **Pool lifecycle**
- `PoolService` keeps **one pool per type**; it does not guard against duplicate `AddPool<T>()` calls (duplicate adds throw from `Dictionary.Add`).
- `GameObjectPool.Dispose(bool)` destroys the `SampleEntity` GameObject; `GameObjectPool.Dispose()` destroys pooled instances but does not necessarily destroy the sample reference—be explicit about disposal expectations when changing pool behavior.
- `GameObjectPool` and `GameObjectPool<T>` override `CallOnSpawned`/`CallOnDespawned` (virtual methods) to use `GetComponent<IPoolEntitySpawn>()` / `GetComponent<IPoolEntityDespawn>()` for lifecycle hooks on **components**. This differs from `ObjectPool<T>` which casts the entity directly.
- **Version data pipeline**
- Runtime expects a Resources TextAsset named `version-data` (`VersionServices.VersionDataFilename`).
- `VersionEditorUtils` writes `Assets/Configs/Resources/version-data.txt` on editor load and can be invoked before builds. It uses git CLI; failures should be handled gracefully.
- Accessors like `VersionServices.VersionInternal` will throw if version data hasn’t been loaded yet—call `VersionServices.LoadVersionDataAsync()` early (and decide how you want to handle load failures).

## 5. Coding Standards (Unity 6 / C# 9.0)
- **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_EDITOR` if absolutely necessary).
- **Performance**
- Be mindful of allocations in hot paths (e.g., `PublishSafe` allocates; tick lists mutate; avoid per-frame allocations).

## 6. External Package Sources (for API lookups)
Prefer local UPM cache / local packages when needed:
- DataExtensions: `Packages/com.gamelovers.dataextensions/` (e.g., `floatP`)
- Unity Newtonsoft JSON (Unity package): check `Library/PackageCache/` if you need source details

## 7. Dev Workflows (common changes)
- **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`/`CoroutineService` pattern (single `DontDestroyOnLoad` host object + `Dispose()`).
- **Bind/resolve services**
- Bind instances via `MainInstaller.Bind<IMyService>(myServiceInstance)`.
- Resolve via `MainInstaller.Resolve<IMyService>()` or `TryResolve`.
- Clear bindings on reset via `MainInstaller.Clean()` (or `Clean<T>()` / `CleanDispose<T>()`).
- **Update versioning**
- Ensure `version-data.txt` exists/updates correctly in `Assets/Configs/Resources/`.
- If changing `VersionServices.VersionData`, update both runtime parsing and `VersionEditorUtils` writing logic.

## 8. Update Policy
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 like `floatP`)

7 changes: 7 additions & 0 deletions AGENTS.md.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2026-01-11

**New**:
- Added *AGENTS.md* document to help guide AI coding assistants to understand and work with this package library
- Added an entire test suit of unit/integration/performance/smoke tests to cover all the code for all services in this package

**Changed**:
- Changed *VersionServices* namespace from *GameLovers* to *GameLovers.Services* to maintain consistency with other services in the package.
- Made *CallOnSpawned*, *CallOnSpawned\<TData\>*, and *CallOnDespawned* methods virtual in *ObjectPoolBase\<T\>* to allow derived pool classes to customize lifecycle callback behavior.

**Fixed**:
- Fixed the *README.md* file to now follow best practices in OSS standards for Unity's package library projects
- Fixed linter warnings in *VersionServices.cs* (redundant field initialization, unused lambda parameter, member shadowing)
- Fixed *GameObjectPool* not invoking *IPoolEntitySpawn.OnSpawn()* and *IPoolEntityDespawn.OnDespawn()* on components attached to spawned GameObjects.

## [0.15.1] - 2025-09-24

**New**:
Expand Down
14 changes: 7 additions & 7 deletions Editor/VersionEditorUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using UnityEditor;
using UnityEngine;
using GameLovers.Services;

namespace GameLovers.Services.Editor
{
Expand All @@ -12,6 +13,8 @@ namespace GameLovers.Services.Editor
public static class VersionEditorUtils
{
private const int ShortenedCommitLength = 8;
private const string AssetsPath = "Assets";
private const string FilePath = "Configs/Resources";

/// <summary>
/// Set the internal version before building the app.
Expand Down Expand Up @@ -87,7 +90,7 @@ private static VersionServices.VersionData GenerateInternalVersionSuffix(bool is
}
else
{
data.Commit = commitHash.Substring(0, ShortenedCommitLength);
data.CommitHash = commitHash.Substring(0, ShortenedCommitLength);
}
}
}
Expand All @@ -110,10 +113,7 @@ private static VersionServices.VersionData GenerateInternalVersionSuffix(bool is
/// </summary>
private static void SaveVersionData(string serializedData)
{
const string assets = "Assets";
const string resources = "Build/Resources";

var absDirPath = Path.Combine(Application.dataPath, resources);
var absDirPath = Path.Combine(Application.dataPath, FilePath);
if (!Directory.Exists(absDirPath))
{
Directory.CreateDirectory(absDirPath);
Expand All @@ -125,7 +125,7 @@ private static void SaveVersionData(string serializedData)
if (File.Exists(Path.ChangeExtension(absFilePath, assetExtension)))
{
AssetDatabase.DeleteAsset(
Path.Combine(assets, resources,
Path.Combine(AssetsPath, FilePath,
Path.ChangeExtension(VersionServices.VersionDataFilename, assetExtension)));
}

Expand All @@ -134,7 +134,7 @@ private static void SaveVersionData(string serializedData)
File.WriteAllText(Path.ChangeExtension(absFilePath, textExtension), serializedData);

AssetDatabase.ImportAsset(
Path.Combine(assets, resources,
Path.Combine(AssetsPath, FilePath,
Path.ChangeExtension(VersionServices.VersionDataFilename, textExtension)));
}
}
Expand Down
Loading
Loading