Releases: RimuruDev/AbyssMothNodeFramework
v3.1.2
What's Changed
- feat: Add init trace diagnostics, node context helpers, and warning c… by @RimuruDev in #11
Full Changelog: v3.1.0...v3.1.2
v3.1.0
What's Changed
- feat!: release v3.0.0 core architecture refresh by @RimuruDev in #10
Full Changelog: v3.0.0...v3.1.0
v3.0.0
Full Changelog: 2.3.14...v3.0.0
- replace ServiceRegistry with ServiceContainer and add tagged service API
- migrate SceneEntityIndex to LocalConnector-first indexing with runtime-safe tag refresh
- remove EntityKeyBehaviour and legacy debug config tooling
- add FrameworkConfig + FrameworkLogger and improve SceneConnector spawn/collection flow
- add EditMode/PlayMode tests, examples, and Russian docs
- isolate benchmark assembly to Editor and move benchmark logs to docs
v2.2.10
Full Changelog: v2.2.9...v2.2.10
v2.2.7
Full Changelog: v2.2.6...v2.2.7
v2.2.4
What's Changed
- v2.0.0 by @RimuruDev in #5
Full Changelog: v2.0.0...v2.2.4
AbyssMoth NodeFramework — Cookbook (v2.2.4)
0) Старт игры: кто кого запускает
Boot цепочка:
AppEntryсоздаётGlobalRootи вешаетSceneOrchestrator(DontDestroyOnLoad)SceneOrchestratorподнимаетProjectRootConnector(из Resources, если надо)ProjectRootConnector.Awake()создаётProjectContextи вызываетExecute(ProjectContext, sender: this)- На каждый
sceneLoadedSceneOrchestratorвызываетSceneConnector.Execute(projectRoot.ProjectContext)для сцены
1) Контейнеры: где какие сервисы живут
ProjectContext (глобальный, живёт всегда)
- Хост:
ProjectRootConnector.ProjectContext - Доступ откуда угодно:
ProjectRootRegistry.GetContext()ProjectServices.ContextProjectServices.Get<T>() / TryGet<T>() / Add<T>()
SceneContext (на сцену, родитель = ProjectContext)
- Хост:
SceneConnector.SceneContext - Создаётся внутри
SceneConnector.Execute(projectContext)какnew ServiceRegistry(parentContainer: projectContext) - Внутри сцены любой нод получает
ServiceRegistry registryвBind/Construct/...— это и есть SceneContext
Важно
- У
ServiceRegistryнет публичного доступа к parent — и не надо:TryGet/Getавтоматически поднимаются вверх. - Если ты в SceneContext вызываешь
registry.Get<SomeGlobalService>(), он спокойно найдёт его в ProjectContext.
2) ServiceRegistry: как регать/получать сервисы
Внутри нода (у тебя есть registry)
public sealed class AudioBootstrapNode : ConnectorNode
{
public override void Bind(ServiceRegistry registry)
{
registry.Add(new AudioService());
}
protected override void DisposeInternal()
{
// если надо гарантированно убрать именно свой инстанс:
// registry.RemoveIfSame(expected: audioService);
}
}Глобально (ProjectContext) из любого места
var lifecycle = ProjectServices.Get<AppLifecycleService>();
ProjectServices.Add(new AnalyticsService());
var analytics = ProjectServices.Get<AnalyticsService>();Когда нужен TryGet, а когда Get
TryGet— если сервис опциональный / модульныйGet— если сервис “обязан существовать” (например аналитика в сборке с аналитикой)
3) Жизненный цикл нода (ConnectorNode)
Порядок вызовов при Execute(LocalConnector)
Bind(registry)Construct(registry)BeforeInit()Init()AfterInit()- затем циклы:
Tick / FixedTick / LateTick
Где что делать
Bind— сохранить ссылки на registry, добавить/получить сервисы, базовые подпискиConstruct— создать “тяжёлые” штуки, которые не зависят от других нодовBeforeInit/Init/AfterInit— логика инициализации в 3 фазы (аналог Awake/Start/после прогрева)DisposeInternal— отписки и чистка (всё что подписывал — отписать)
Шаблон нода
public sealed class MyNode : ConnectorNode
{
private ServiceRegistry registry;
public override void Bind(ServiceRegistry registry)
{
this.registry = registry;
}
public override void Construct(ServiceRegistry registry) { }
public override void BeforeInit() { }
public override void Init() { }
public override void AfterInit() { }
public override void Tick(float deltaTime) { }
public override void FixedTick(float fixedDeltaTime) { }
public override void LateTick(float deltaTime) { }
protected override void DisposeInternal()
{
// отписки
}
}Про Unity callbacks
В нодах держи логику в Bind/Init/Tick.... (В дебаге у тебя есть валидация, которая ругается на Awake/Start/Update у нодов.)
4) Порядок выполнения (Order)
Ноды внутри LocalConnector
-
Сортировка:
Order(IOrder), затем по имени типа -
Где задавать:
- в инспекторе у нода (
ConnectorNodeуже имеет поле Order) - или в
OnValidateконкретного нода (Editor-only)
- в инспекторе у нода (
Коннекторы в SceneConnector
- Сортировка:
Order, затем по имени объекта
5) LocalConnector: Tick/Pause/Resume/Dispose
Пауза тиков у коннектора (и рассылка в IPausable ноды)
connector.OnPauseRequest(sender: this); // выключит EnabledTicks и вызовет OnPauseRequest у нодов
connector.OnResumeRequest(sender: this); // включит обратно и вызовет OnResumeRequestRunWhenDisabled
- Если нод отключён (
isActiveAndEnabled == false), он всё равно может тикать, если этоConnectorNodeи у него включёнRunWhenDisabled.
Dispose
-
LocalConnector.Dispose():- отписывается от сцены
- вызывает
Dispose()у нодов сIDispose - чистит кэши tick интерфейсов
6) SceneConnectorRegistry: как получить SceneContext извне нодов
using UnityEngine.SceneManagement;
var scene = SceneManager.GetActiveScene();
if (SceneConnectorRegistry.TryGet(scene, out var sceneConnector))
{
var sceneContext = sceneConnector.SceneContext;
var sceneIndex = sceneContext.Get<SceneEntityIndex>();
}7) SceneEntityIndex: поиск по id / tag / типу нода
Как объект попадает в индекс
SceneConnectorпри Execute регает все статические коннекторы- Динамические коннекторы регаются через
LocalConnector.OnEnable()если сцена уже инициализирована - Для id/tag нужен
EntityKeyBehaviourна том же объекте, что иLocalConnector
Поиск коннектора по Id
var sceneIndex = registry.Get<SceneEntityIndex>();
if (sceneIndex.TryGetById(42, out var connector))
{
// ок
}
var mustExist = sceneIndex.GetByIdOrThrow(42);Поиск по Tag
if (sceneIndex.TryGetFirstByTag("Chest", out var chest))
{
// первый попавшийся
}
var all = sceneIndex.GetAllByTag("Chest"); // IReadOnlyList<LocalConnector>
var mustExist = sceneIndex.GetFirstByTagOrThrow("Chest");Поиск нода по типу (в любой LocalConnector сцены)
if (sceneIndex.TryGetFirstNode<MyNode>(out var node, includeDerived: true))
{
// найден
}
var mustExist = sceneIndex.GetFirstNodeOrThrow<MyNode>(includeDerived: true);Получить все ноды типа
var buffer = new List<MyNode>(64);
var count = sceneIndex.GetNodes(buffer, includeDerived: true);Найти нод внутри конкретного коннектора
if (sceneIndex.TryGetNodeInConnector<MyNode>(connector, out var node))
{
}Найти нод в первом коннекторе по тегу
if (sceneIndex.TryGetNodeInFirstByTag<MyNode>("Chest", out var node))
{
}8) EntityKeyBehaviour: Id/Tag
-
Поля:
Id(int)Tag(string)AutoAssignId(bool)
-
Editor: кнопка Assign Unique Id (для статических id в сценах)
-
Runtime: если
Id <= 0иAutoAssignId == true, индекс может назначить id при регистрации
9) Динамические объекты (spawn/disable/pool)
Что происходит автоматически
-
Если ты заспавнил объект с
LocalConnector:- при
OnEnable()он попробует зарегаться вSceneConnectorи выполниться (если сцена уже initialized)
- при
-
При
OnDisable()— разрегистрируется -
Execute()уLocalConnectorвызывается один раз (есть флаг executed)
Если нужен “полный снос” объекта с корректным Dispose
Используй утилиту:
ConnectorDestroyUtils.DisposeAndDestroy(gameObject);10) Переходы между сценами через EmptySceneTransition + cleanup
В проекте есть SceneTransitionService:
-
Go(targetSceneName, doCleanup) -
грузит
transitionSceneName -
опционально делает:
Resources.UnloadUnusedAssets()GC.Collect()+WaitForPendingFinalizers()+GC.Collect()
-
потом грузит целевую сцену
Как зарегистрировать SceneTransitionService один раз (в ProjectContext)
Сделай нод на ProjectRootConnector (он DontDestroy) и зарегай сервис в Bind:
public sealed class SceneTransitionsBootstrapNode : ConnectorNode
{
[SerializeField] private string transitionSceneName = "EmptySceneTransition";
public override void Bind(ServiceRegistry registry)
{
// runner = этот нод (MonoBehaviour), он живёт вместе с ProjectRootConnector
ProjectServices.Add(new SceneTransitionService(runner: this, transitionSceneName: transitionSceneName));
}
}Как вызывать переход из любого места
ProjectServices.Get<SceneTransitionService>().Go("Level_2", doCleanup: true);Примечание про SceneConnector в transition-сцене
- В
EmptySceneTransitionможно не иметьSceneConnector. - В Editor у тебя может быть warn, если
SceneConnectorне найден — либо игнорируй, либо добавь пустойSceneConnectorв transition-сцену.
11) AppLifecycleService: фокус/пауза/выход
SceneOrchestrator гарантирует наличие AppLifecycleService в ProjectContext.
Подписка из нода
public sealed class LifecycleListenerNode : ConnectorNode
{
private AppLifecycleService lifecycle;
public override void Bind(ServiceRegistry registry)
{
lifecycle = ProjectServices.Get<AppLifecycleService>();
lifecycle.FocusChanged += OnFocusChanged;
lifecycle.PauseChanged += OnPauseChanged;
lifecycle.Quit += OnQuit;
}
protected override void DisposeInternal()
{
if (lifecycle == null)
return;
lifecycle.FocusChanged -= OnFocusChanged;
lifecycle.PauseChanged -= OnPauseChanged;
lifecycle.Quit -= OnQuit;
}
private void OnFocusChanged(bool hasFocus, Object sender) { }
private void OnPauseChanged(bool paused, Object sender) { }
private void OnQuit(Object sender) { }
}...
v2.0.0 -> Tools and fixed memory leak
What's Changed
- Entity features by @RimuruDev in #3
- Sync v1.4.6 EntityFeatures by @RimuruDev in #4
Full Changelog: v1.4.6...v2.0.0
v1.4.6 Entity Features
Full Changelog: v1.3.5...v1.4.6
v1.3.5
What's Changed
- v1.3.5 by @RimuruDev in #1
- v1.3.5 by @RimuruDev in #2
New Contributors
- @RimuruDev made their first contribution in #1
Full Changelog: v1.0.0...v1.3.5
AbyssMothNodeFramework_v1.0.0
Dependencies: NaugntyAttribute -> assetstore
Full Changelog: https://github.com/RimuruDev/AbyssMothNodeFramework/commits/v1.0.0