From 8d599a6396d535e66bf9961566966e052a668007 Mon Sep 17 00:00:00 2001 From: Savya Bikram Shah Date: Tue, 26 May 2026 17:35:06 +0545 Subject: [PATCH] docs added --- Assets/Darkmatter/Code/Features.meta | 8 + Readme.md | 543 +++++++++++++++++++++++++++ 2 files changed, 551 insertions(+) create mode 100644 Assets/Darkmatter/Code/Features.meta diff --git a/Assets/Darkmatter/Code/Features.meta b/Assets/Darkmatter/Code/Features.meta new file mode 100644 index 0000000..d31d16b --- /dev/null +++ b/Assets/Darkmatter/Code/Features.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 43ea0ae4e7f8a4f108088032adaaccd6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Readme.md b/Readme.md index 7622941..23571d4 100644 --- a/Readme.md +++ b/Readme.md @@ -1047,3 +1047,546 @@ Toddler-mode error UI: | `ColorBookLifetimeScope`, `AppBoot` | App | `Darkmatter.App` | If a class's natural home doesn't match its asmdef, the architecture is bent — fix the placement, don't add a reference. + +--- + +## 32. Class Reference (Detailed) + +Canonical breakdown of every concrete class and interface. For each: **purpose**, **public surface** (signatures), **injected dependencies**, and **collaborators** (signals or interfaces it talks to). + +> Convention used below +> - `// fields:` = constructor-injected dependencies +> - `// pub:` = events / signals fired +> - `// sub:` = events / signals consumed +> - All async returns are `UniTask` unless noted. + +--- + +### 32.1 Core Contracts + +Pure interfaces and DTOs. Zero logic. + +#### `IDrawingTemplate` *(Core/Drawing)* +Immutable view of a single drawing's authored data. +```csharp +public interface IDrawingTemplate { + string Id { get; } // e.g. "animals/elephant" + string DisplayName { get; } // user-facing + Sprite Thumbnail { get; } // 256×256 for catalog grid + Sprite PaperBackground { get; } // composited under artwork + IReadOnlyList Pieces { get; } // for ShapeBuilder + IReadOnlyList Regions { get; } // for Coloring +} +``` +Implemented by `DrawingTemplateSO` (ScriptableObject) loaded via Addressables. + +#### `IDrawingTemplateCatalog` *(Core/Drawing)* +Authority on which drawings exist, completion state, and "next" selection. +```csharp +public interface IDrawingTemplateCatalog { + UniTask InitializeAsync(); // pulls manifest from Addressables + IReadOnlyList AllTemplateIds { get; } + UniTask GetThumbnailAsync(string id); // cheap; for grid + UniTask LoadAsync(string id); // expensive; full template + void Release(string id); // Addressables ref count down + string NextUnseen(string currentId); // progression hint +} +``` + +#### `IColorPalette` *(Core/Coloring)* +Set of colors offered to the child. Authored as `ColorPaletteSO`. +```csharp +public interface IColorPalette { + string Id { get; } + IReadOnlyList Colors { get; } // 6–10 entries typical +} +``` + +#### `ICommand` & `IUndoStack` *(Core/History)* +Already shown in section 8. Each undoable user action is one `ICommand`; the stack is bounded. + +#### `IGalleryService` *(Core/Gallery)* +Persistent store of saved artwork PNGs. +```csharp +public interface IGalleryService { + UniTask SaveAsync(byte[] png, string templateId); + UniTask> ListAsync(); // sorted newest first + UniTask LoadFullAsync(string artworkId); // for fullscreen view + UniTask LoadThumbnailAsync(string artworkId); + UniTask DeleteAsync(string artworkId); +} +``` + +#### `ICaptureService` *(Core/Capture)* +Snapshots the artwork camera to a PNG blob. +```csharp +public interface ICaptureService { + UniTask CaptureAsync( + Camera artCamera, + Sprite paperBackground, + int width = 2048, + int height = 2048); +} +``` + +#### `IProgressionService` *(Core/Progression)* +Tracks which templates the child has completed and what they last opened. +```csharp +public interface IProgressionService { + UniTask LoadAsync(); + UniTask SaveAsync(); + IReadOnlyCollection CompletedTemplateIds { get; } + string LastOpenedTemplateId { get; } + void MarkCompleted(string templateId); + void SetLastOpened(string templateId); +} +``` + +#### `IAssetProviderService` *(Core/Assets)* +Addressables wrapper. Hides handle bookkeeping from features. +```csharp +public interface IAssetProviderService { + UniTask InitializeAsync(); + UniTask LoadAsync(string address) where T : UnityEngine.Object; + UniTask> LoadByLabelAsync(string label) where T : UnityEngine.Object; + void Release(string address); + void ReleaseAll(); +} +``` + +#### `IEventBus` *(Libs/EventBus, also referenced from Core)* +```csharp +public interface IEventBus { + void Publish(T signal) where T : struct; + IDisposable Subscribe(Action handler) where T : struct; +} +``` +Signals are structs to avoid GC. Disposable subscription so presenters can unsubscribe in `Dispose()`. + +--- + +### 32.2 Services Layer + +Concrete infrastructure. One implementation each. All singletons in `RootLifetimeScope`. + +#### `AddressableAssetProviderService` *(Services/Assets)* +Implements `IAssetProviderService`. +- **Responsibility:** Wrap `Addressables.LoadAssetAsync` and ref-count handles by address. +- **State:** `Dictionary` keyed by address. +- **Notes:** `Release(address)` decrements; `ReleaseAll()` for scene teardown. Initialization must complete before any other service may load. + +#### `FileGalleryService` *(Services/Gallery)* +Implements `IGalleryService`. +```csharp +// fields: +// IPathProvider _paths (wraps Application.persistentDataPath for tests) +// IThumbnailGenerator _thumb (downscale + encode) +// IEventBus _bus +// pub: ArtworkSavedSignal, ArtworkDeletedSignal +``` +- **Save flow:** write `{guid}.png.tmp` → fsync → rename; generate thumbnail on a worker; write sidecar JSON last (so partial saves are detectable by absence of JSON). +- **List flow:** enumerate `*.json` in `Gallery/`, deserialize, sort by `CreatedUtc desc`. +- **Delete flow:** delete png + thumb + json; missing files ignored (idempotent). + +#### `RenderTextureCaptureService` *(Services/Capture)* +Implements `ICaptureService`. +- **Steps:** allocate `RenderTexture(width, height, 0, ARGB32)` → bind to `artCamera.targetTexture` → `artCamera.Render()` → `ReadPixels` into `Texture2D` → composite `paperBackground` underneath (single shader blit) → `EncodeToPNG` → release RT + textures. +- **Threading:** PNG encode happens on a `UniTask.RunOnThreadPool` to avoid hitching the main thread on tablets. +- **Sizing:** default 2048², overridable. Capped at device max texture size. + +#### `JsonPersistenceService` *(Services/Persistence)* +Implements `IPersistenceService` (small JSON blob; not the gallery). +```csharp +public interface IPersistenceService { + UniTask LoadAsync(string key) where T : class, new(); + UniTask SaveAsync(string key, T value); +} +``` +- **Path:** `Application.persistentDataPath/save.json`. +- **Format:** single JSON object keyed by `key` so multiple services can share one file. +- **Atomicity:** write to `save.json.tmp` → rename. + +#### `SceneService` *(Services/Scenes)* +Implements `ISceneService`. Wraps `SceneManager.LoadSceneAsync` with `UniTask` plus a fade-curtain. +```csharp +public interface ISceneService { + UniTask LoadAsync(string sceneName, LoadSceneMode mode = LoadSceneMode.Single); + UniTask UnloadAsync(string sceneName); +} +``` + +#### `AudioService` *(Services/Audio)* +Implements `IAudioService`. Plays SFX clips loaded by address, mixes via Unity AudioMixer groups. +```csharp +public interface IAudioService { + UniTask PreloadAsync(string label); // e.g. "sfx,ui" + void PlayOneShot(string clipId, float volume = 1f); + void SetCategoryVolume(AudioCategory cat, float v01); +} +``` +Holds an internal `Dictionary` populated at preload. + +#### `InputReaderSO` *(Services/Inputs)* +ScriptableObject wrapping the new Input System; exposes events. +```csharp +public interface IInputReader { + event Action PointerDown; // world pos + event Action PointerDrag; + event Action PointerUp; +} +``` +- **Why an SO:** assignable in inspector and survives scene loads, but still resolvable via DI (`builder.RegisterInstance(_inputReader).As()`). + +--- + +### 32.3 Libs + +Generic, project-agnostic utilities. + +#### `BoundedUndoStack` *(Libs/CommandStack)* +Implements `IUndoStack`. Source already in section 24. +- **Capacity:** default 20. +- **Invariant:** `_redo` cleared on any new `Push`. +- **Edge cases:** `Undo`/`Redo` on empty stack is a no-op (never throws). + +#### `EventBus` *(Libs/EventBus)* +Implements `IEventBus` with a `Dictionary` of `Action` per signal type. +- **Subscribe** returns an `IDisposable` that removes the handler on `Dispose`. +- **Publish** snapshots the invocation list before iterating (so handlers may safely unsubscribe during dispatch). + +#### `Fsm` *(Libs/FSM)* +Generic state machine used by `ColorBookFlowController`. +```csharp +public sealed class Fsm where TState : struct, Enum { + public TState Current { get; } + public event Action Transitioned; + public void Bind(TState state, IFsmState handler); + public void Go(TState next); // calls Exit on old, Enter on new +} + +public interface IFsmState { void Enter(); void Exit(); } +``` + +--- + +### 32.4 Feature — `DrawingCatalog` + +#### `DrawingCatalogController` *(Systems)* +Headless logic. Owns the list of template IDs visible in the grid. +```csharp +// fields: IDrawingTemplateCatalog _catalog, IEventBus _bus +public sealed class DrawingCatalogController : IAsyncStartable { + public IReadOnlyList VisibleIds { get; } + public event Action ListChanged; + public UniTask StartAsync(CancellationToken ct); // pulls catalog, refreshes list + public void OnTemplateSelected(string id); // bus.Publish(new DrawingSelectedSignal(id)) +} +// pub: DrawingSelectedSignal +``` + +#### `DrawingCatalogPresenter` *(UI)* +Bridges controller ↔ view. +```csharp +// fields: IDrawingCatalogView _view, DrawingCatalogController _ctrl, IDrawingTemplateCatalog _catalog +public sealed class DrawingCatalogPresenter : IStartable, IDisposable { + public void Start(); // wires view.OnItemClicked → _ctrl.OnTemplateSelected + public void Dispose(); +} +``` +- **Thumbnail load:** `_catalog.GetThumbnailAsync(id)` per visible cell, with placeholder while loading. + +#### `DrawingCatalogView : MonoBehaviour` *(UI)* +Implements `IDrawingCatalogView`. Pure setters + click event. +```csharp +public interface IDrawingCatalogView { + event Action OnItemClicked; + void SetItems(IReadOnlyList items); // vm = id + Sprite thumbnail +} +``` + +--- + +### 32.5 Feature — `ShapeBuilder` + +#### `ShapeBuilderController` *(Systems)* +Spawns shape pieces for the selected template, tracks snap progress, fires `ShapeAssembledSignal` when complete. +```csharp +// fields: IDrawingTemplateCatalog _catalog, ShapePieceFactory _factory, IEventBus _bus, ShapeBuilderConfig _cfg +public sealed class ShapeBuilderController : IDisposable { + public IReadOnlyList Active { get; } + public UniTask BuildAsync(string templateId); // load template, spawn pieces in tray + public void Reset(); // clear, unsubscribe +} +// sub: DrawingSelectedSignal +// pub: ShapeAssembledSignal +``` +- **Internal:** counts `PieceSnappedSignal` against expected piece count. + +#### `ShapePieceView : MonoBehaviour` *(Views)* +World-space draggable sprite with collider. Source for snap-or-return logic shown in section 26. +```csharp +public sealed class ShapePieceView : MonoBehaviour { + public string PieceId { get; } + public bool IsLocked { get; } + public event Action Snapped; // raised when piece locks into slot + public void Initialize(ShapePieceDTO dto, IInputReader input, IAudioService audio); +} +``` +- **No public mutators** for position once locked — controller treats `IsLocked` as the source of truth. + +#### `ShapePieceFactory` *(Systems)* +Instantiates `ShapePieceView` prefabs from a pool. Avoids re-instantiating across "Next" cycles on the same template family. +```csharp +public sealed class ShapePieceFactory { + public ShapePieceView Spawn(ShapePieceDTO dto, Transform parent); + public void Despawn(ShapePieceView view); +} +``` + +--- + +### 32.6 Feature — `Coloring` + +#### `ColoringStateRepository` *(Repository)* +In-memory model. Owns "currently selected color" and the palette in use. +```csharp +public sealed class ColoringStateRepository { + public IColorPalette Palette { get; private set; } + public int SelectedIndex { get; private set; } + public Color CurrentColor => Palette.Colors[SelectedIndex]; + public event Action SelectedIndexChanged; + public void SetPalette(IColorPalette palette); // resets SelectedIndex to 0 + public void SelectColor(int index); +} +``` +- **Why a repository:** presenter and controller both need to read/write current color; an event-emitting POCO is simpler than wiring two signals. + +#### `ColoringController` *(Systems)* — implements `IColoringController` +Builds and pushes `PaintRegionCommand` instances; spawns `ColorRegionView` per region. +```csharp +// fields: IUndoStack _undo, ColoringStateRepository _state, ColorRegionFactory _factory, IEventBus _bus +public interface IColoringController { + UniTask SpawnRegionsAsync(IDrawingTemplate template); + void PaintRegion(ColorRegionView view); // builds command, pushes to undo stack + void Clear(); +} +// sub: ShapeAssembledSignal (via flow controller, not direct) +// pub: ColorAppliedSignal (via PaintRegionCommand) +``` + +#### `ColorRegionView : MonoBehaviour` *(Views)* +Sprite + `PolygonCollider2D`, on `Artwork` layer. Tapped via `Physics2D.OverlapPoint` from `ColoringInputBinder`. +```csharp +public sealed class ColorRegionView : MonoBehaviour { + public string RegionId { get; } + public Color Color { get; } // current paint + public void Initialize(ColorRegionDTO dto); + public void SetColor(Color c); // setter only; no logic +} +``` + +#### `ColoringInputBinder` *(Systems)* — `IStartable, IDisposable` +Subscribes to `IInputReader.PointerDown`, raycasts on the `Artwork` layer mask, calls `ColoringController.PaintRegion(view)` on hit. + +#### `PaintRegionCommand` *(Commands)* +Source in section 23. Holds `view`, `fromColor`, `toColor`, `bus`. Symmetrical execute/undo. + +#### `ColorPaletteView`, `ColorPalettePresenter` *(UI)* +Sources in section 25. Presenter binds `ColoringStateRepository.SelectedIndexChanged` ↔ `IColorPaletteView`. + +#### `ColorRegionFactory` *(Systems)* +Mirror of `ShapePieceFactory` for regions. Pool-friendly. + +--- + +### 32.7 Feature — `History` + +#### `HistoryController` *(Systems)* — `IStartable, IDisposable` +Owns the per-session `IUndoStack` (registered scoped, so a new ColorBook scene = new stack). +```csharp +// fields: IUndoStack _stack, IEventBus _bus +public sealed class HistoryController : IStartable, IDisposable { + public bool CanUndo => _stack.CanUndo; + public bool CanRedo => _stack.CanRedo; + public event Action StateChanged; + public void Undo(); // _stack.Undo() + StateChanged + public void Redo(); + // sub: DrawingSelectedSignal → _stack.Clear() +} +``` + +#### `HistoryButtonsView : MonoBehaviour` *(UI)* +Two big arrow buttons. Setters only. +```csharp +public interface IHistoryButtonsView { + event Action UndoClicked; + event Action RedoClicked; + void SetUndoEnabled(bool enabled); + void SetRedoEnabled(bool enabled); +} +``` + +#### `HistoryPresenter` *(UI)* +Wires controller `StateChanged` ↔ view enable/disable; view click events → controller. + +--- + +### 32.8 Feature — `Capture` + +#### `CaptureController` *(Systems)* +The orchestrator behind the "Capture" button. Stateless other than guarding against concurrent captures. +```csharp +// fields: ICaptureService _capture, IGalleryService _gallery, IEventBus _bus, ColorBookSceneRefs _refs +public sealed class CaptureController { + public bool IsCapturing { get; } + public UniTask CaptureCurrentAsync(string templateId, Sprite paperBg); +} +// pub: ArtworkCapturedSignal (mid-flow), ArtworkSavedSignal (post-save) +``` +- **Concurrency:** sets `IsCapturing = true` on entry; UI binds button enabled to `!IsCapturing` to prevent double-tap. + +#### `CaptureButtonPresenter` *(UI)* +Wires button click → `CaptureController.CaptureCurrentAsync`. Disables button while in progress. Shows toast on `ArtworkSavedSignal`. + +--- + +### 32.9 Feature — `Progression` + +#### `ProgressionService` *(Systems)* — implements `IProgressionService` +The only place that knows what "completed" means. +- **Persistence:** delegates to `IPersistenceService` under key `"progression"`. +- **Load order:** `AppBoot` calls `LoadAsync()` early. +- **Save trigger:** after `MarkCompleted`, debounced 500 ms to coalesce a burst of "Next" presses. + +#### `ProgressionRepository` *(Repository)* +Pure in-memory holder used by the service. Separated so tests can inspect state without going through file IO. + +--- + +### 32.10 Feature — `ColorBookFlow` + +#### `ColorBookFlowController` *(Systems)* — `IStartable, IDisposable` +**The only orchestrator inside the ColorBook scene.** Drives the panel FSM: `Catalog → Building → Coloring → Done`. +```csharp +// fields: +// IEventBus _bus +// IDrawingTemplateCatalog _catalog +// ShapeBuilderController _builder +// IColoringController _coloring +// CaptureController _capture +// IProgressionService _progression +// ColorBookSceneRefs _refs (panel roots to enable/disable) +// Fsm _fsm +``` +- **State table:** + + | State | On enter | Triggers exit | + |------------|-----------------------------------------------|--------------------------------| + | `Catalog` | Show catalog panel | `DrawingSelectedSignal` | + | `Building` | `_builder.BuildAsync(id)` | `ShapeAssembledSignal` | + | `Coloring` | `_coloring.SpawnRegionsAsync(template)` | "Next" or "Capture" pressed | + | `Done` | Run autosave capture, mark completed, `Go(Catalog)` for next | always advances | + +- **"Next" sequence:** `_capture.CaptureCurrentAsync` → `_progression.MarkCompleted` → `_catalog.Release(current)` → `_catalog.LoadAsync(_catalog.NextUnseen(current))` → re-enter `Building`. + +--- + +### 32.11 Feature — `ArtBook` + +#### `GalleryPresenter` *(UI)* — `IAsyncStartable, IDisposable` +Lists artworks, opens fullscreen view, deletes, shares. +```csharp +// fields: IGalleryService _gallery, IGalleryView _view, IExternalShareService _share, IEventBus _bus +``` +- **Start:** `_gallery.ListAsync()` → `_view.SetItems(...)`. +- **Subscribes** to `ArtworkSavedSignal` to live-refresh if the user pops back in. + +#### `IGalleryView` *(UI)* +```csharp +public interface IGalleryView { + event Action OnArtworkTapped; + event Action OnDeleteRequested; + event Action OnShareRequested; + void SetItems(IReadOnlyList items); + void ShowFullscreen(Texture2D full); + void HideFullscreen(); +} +``` + +#### `IExternalShareService` *(Core)* +Platform plugin shim (iOS Photos / Android MediaStore). +```csharp +public interface IExternalShareService { + UniTask SaveToCameraRollAsync(byte[] png); + UniTask ShareAsync(byte[] png, string subject); +} +``` + +--- + +### 32.12 App Layer + +#### `AppBoot` *(App/Boot)* — `IAsyncStartable` +Single entry point. Steps in section 29. +```csharp +// fields: IAssetProviderService _assets, IPersistenceService _persist, IProgressionService _progress, +// IAudioService _audio, ISceneService _scenes, BootConfig _cfg +public sealed class AppBoot : IAsyncStartable { + public UniTask StartAsync(CancellationToken ct); +} +``` + +#### LifetimeScopes +- `RootLifetimeScope` — section 21. Registers all services + `IEventBus`. Persists for app lifetime. +- `MainMenuLifetimeScope` — registers `MainMenuPresenter` and view. +- `ColorBookLifetimeScope` — section 21. Registers feature installers + `ColorBookFlowController` as entry point. +- `ArtBookLifetimeScope` — registers `GalleryPresenter` + view + `IExternalShareService`. + +All scope classes are thin: serialized fields for scene refs, `Configure(IContainerBuilder)` only. + +--- + +### 32.13 Cross-cutting types + +#### `ColorBookSceneRefs : MonoBehaviour` *(App)* +Aggregates all scene-bound Unity references that features need: `Camera artCamera`, `Transform catalogRoot`, `Transform builderRoot`, `Transform coloringRoot`, `RectTransform hudRoot`, `ColorPaletteView paletteView`, `HistoryButtonsView historyView`. Registered as a singleton in `ColorBookLifetimeScope` so features don't `Find` things. + +#### `IInstaller` *(App)* +```csharp +public interface IInstaller { void Install(IContainerBuilder builder); } +``` +Implemented as `ScriptableObject` per feature so scopes can drag them in the inspector (section 22). + +--- + +### 32.14 Class summary table + +| Class | Layer | Role | Key dependencies | +|---|---|---|---| +| `AppBoot` | App | Startup sequencer | assets, persist, progression, scenes | +| `RootLifetimeScope` | App | Root DI | configs | +| `ColorBookLifetimeScope` | App | Scene DI | scene refs, installers | +| `DrawingCatalogController` | Feature | Grid logic | catalog, bus | +| `DrawingCatalogPresenter` | Feature | UI bridge | view, controller, catalog | +| `ShapeBuilderController` | Feature | Piece spawn + snap tracking | catalog, factory, bus, cfg | +| `ShapePieceView` | Feature | Draggable piece MB | input, audio | +| `ColoringStateRepository` | Feature | Current color model | — | +| `ColoringController` | Feature | Region spawn + paint cmd | undo, state, factory, bus | +| `ColorRegionView` | Feature | Region sprite MB | — | +| `PaintRegionCommand` | Feature | Undoable paint | view, bus | +| `HistoryController` | Feature | Undo/redo facade | undo stack, bus | +| `CaptureController` | Feature | Capture+save orchestration | capture svc, gallery, bus, refs | +| `ColorBookFlowController` | Feature | Scene FSM | bus, catalog, builder, coloring, capture, progression | +| `GalleryPresenter` | Feature | Art book listing | gallery, share, view, bus | +| `BoundedUndoStack` | Lib | Capped undo store | — | +| `EventBus` | Lib | Pub/sub | — | +| `Fsm` | Lib | Generic FSM | — | +| `AddressableAssetProviderService` | Service | Addressables wrapper | — | +| `FileGalleryService` | Service | Gallery file IO | paths, thumb gen, bus | +| `RenderTextureCaptureService` | Service | PNG render | — | +| `JsonPersistenceService` | Service | Settings/progression IO | — | +| `SceneService` | Service | Async scene loads | — | +| `AudioService` | Service | SFX playback | assets | +| `ProgressionService` | Service | Completion tracking | persistence | + +If you add a class not in this table, add it here in the same PR. This table is the cheap mental-model index — keep it honest.