Files
RebootKit/Runtime/Engine/Code/Simulation/WorldService.cs
2025-06-24 14:45:45 +02:00

169 lines
5.6 KiB
C#

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<SceneInstance> m_SceneInstance;
struct ActorData {
public Actor Actor;
public readonly bool ManagedByAddressabled;
public ActorData(Actor actor, bool managedByAddressabled) {
Actor = actor;
ManagedByAddressabled = managedByAddressabled;
}
}
readonly List<ActorData> m_Actors = new List<ActorData>();
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<Actor>()) {
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<TActor> SpawnActor<TActor>(AssetReferenceT<GameObject> 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);
}
}
}
}
}