using System; using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; using R3; using RebootKit.Engine.Foundation; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.Assertions; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; using Logger = RebootKit.Engine.Foundation.Logger; namespace RebootKit.Engine.Services.Simulation { public interface IWorldContext { } public class WorldService : IService { static readonly Logger s_Logger = new Logger(nameof(WorldService)); enum WorldState { Unloaded, Loading, Loaded } WorldState m_WorldState = WorldState.Unloaded; WorldConfig m_Config; AsyncOperationHandle m_SceneInstance; struct ActorData { public Actor Actor; public readonly bool ManagedByAddressabled; public ActorData(Actor actor, bool managedByAddressabled) { Actor = actor; ManagedByAddressabled = managedByAddressabled; } } readonly List m_Actors = new List(); readonly IDisposable m_UpdateSubscription; public IWorldContext Context { get; private set; } public WorldService() { m_UpdateSubscription = Observable.EveryUpdate() .Subscribe(_ => { Tick(Time.deltaTime); }); } public void Dispose() { m_UpdateSubscription.Dispose(); Unload(); } public async UniTask LoadAsync(WorldConfig worldConfig, CancellationToken cancellationToken) { await UniTask.WaitWhile(() => m_WorldState == WorldState.Loading, cancellationToken: cancellationToken); Unload(); m_WorldState = WorldState.Loading; m_Config = worldConfig; m_SceneInstance = worldConfig.mainScene.LoadSceneAsync(LoadSceneMode.Additive, false); await m_SceneInstance.ToUniTask(cancellationToken: cancellationToken); await m_SceneInstance.Result.ActivateAsync(); SceneManager.SetActiveScene(m_SceneInstance.Result.Scene); foreach (GameObject root in m_SceneInstance.Result.Scene.GetRootGameObjects()) { if (root.TryGetComponent(out IWorldContext worldContext)) { Assert.IsNull(Context, "WorldContext is already set. There should be only one WorldContext in the scene."); Context = worldContext; } foreach (Actor actor in root.GetComponentsInChildren()) { m_Actors.Add(new ActorData(actor, false)); } } m_WorldState = WorldState.Loaded; } public void Unload() { KillAllActors(); if (m_SceneInstance.IsValid()) { m_SceneInstance.Release(); m_SceneInstance = default; } m_WorldState = WorldState.Unloaded; Context = null; } public async UniTask SpawnActor(AssetReferenceT asset, CancellationToken cancellationToken) where TActor : Actor { if (m_WorldState != WorldState.Loaded) { s_Logger.Error("World is not loaded. Cannot spawn actor."); return null; } GameObject gameObject = await Addressables.InstantiateAsync(asset); if (cancellationToken.IsCancellationRequested) { asset.ReleaseInstance(gameObject); return null; } if (gameObject.TryGetComponent(out TActor actor)) { actor.OnSpawned(); actor.IsPlaying = true; m_Actors.Add(new ActorData(actor, true)); return actor; } asset.ReleaseInstance(gameObject); return null; } public void KillActor(Actor actor) { ActorData actorData = default; bool found = false; for (int i = m_Actors.Count - 1; i >= 0; i--) { if (m_Actors[i].Actor == actor) { found = true; actorData = m_Actors[i]; m_Actors.RemoveAt(i); break; } } Assert.IsTrue(found, $"Actor {actor.name} not found in the world actors list."); actor.IsPlaying = false; actor.OnDespawned(); if (actorData.ManagedByAddressabled) { Addressables.ReleaseInstance(actor.gameObject); } } public void KillAllActors() { foreach (ActorData actorData in m_Actors) { actorData.Actor.IsPlaying = false; actorData.Actor.OnDespawned(); if (actorData.ManagedByAddressabled) { Addressables.ReleaseInstance(actorData.Actor.gameObject); } else { UnityEngine.Object.Destroy(actorData.Actor.gameObject); } } m_Actors.Clear(); } void Tick(float deltaTime) { if (m_WorldState != WorldState.Loaded) { return; } foreach (ActorData actorData in m_Actors) { if (actorData.Actor.IsPlaying) { actorData.Actor.OnTick(deltaTime); } } } } }