483 lines
14 KiB
C#
483 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Superlazy;
|
|
using UnityEngine;
|
|
|
|
public class SLGame
|
|
{
|
|
public static SLEntity Session { get; private set; }
|
|
|
|
public static IEnumerable<string> SessionCacheKeys => sessionCache.Keys;
|
|
|
|
public static Camera Camera { get; private set; }
|
|
|
|
private class SessionCache
|
|
{
|
|
public bool validState = false;
|
|
public string id = null;
|
|
public SessionCache parent = null;
|
|
public SLEntity sessionCache;
|
|
public List<Action> sessionHandlers;
|
|
public float duration;
|
|
public bool isChanged;
|
|
}
|
|
|
|
private static bool currValidState = false;
|
|
private static readonly Dictionary<string, SessionCache> sessionCache = new Dictionary<string, SessionCache>();
|
|
private static readonly List<string> sessionCacheIndex = new List<string>();
|
|
|
|
private static int handlerIndex = 0;
|
|
private static readonly Dictionary<Action, int> sessionHandlers = new Dictionary<Action, int>();
|
|
private static readonly Dictionary<Action, int> sessionCounters = new Dictionary<Action, int>();
|
|
private static readonly SortedList<int, Action> runHandlers = new SortedList<int, Action>();
|
|
|
|
private static Dictionary<string, SLGameComponent> gameComponents;
|
|
private static List<SLGameComponent> activeComponents;
|
|
private static List<SLGameComponent> readyActiveComponents;
|
|
|
|
private static Dictionary<string, (SLGameComponent manager, MethodInfo method)> commands;
|
|
private static List<(string command, SLEntity entity)> commandQueue;
|
|
private static bool canCommand = false; // 커맨드 즉시 실행 or queue
|
|
private static bool updateSessionCache = false; // 세션 캐시 업데이트 이후에는 필요시 핸들러를 즉시 실행
|
|
|
|
public static SLEntity SessionGet(string path)
|
|
{
|
|
if (sessionCache.TryGetValue(path, out var cache))
|
|
{
|
|
cache.duration = 2f;
|
|
var parent = cache.parent;
|
|
while (parent != null)
|
|
{
|
|
parent.duration = 2f;
|
|
parent = parent.parent;
|
|
}
|
|
|
|
if (cache.validState == currValidState)
|
|
{
|
|
return cache.sessionCache;
|
|
}
|
|
else
|
|
{
|
|
SLLog.Error($"{path} is expired.");
|
|
return Session.Get(path);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string parentPath = null;
|
|
var subLength = path.LastIndexOf('.');
|
|
if (subLength > 0)
|
|
{
|
|
parentPath = path.Substring(0, subLength);
|
|
SessionGet(parentPath);
|
|
}
|
|
|
|
var ret = Session.Get(path);
|
|
sessionCache[path] = new SessionCache
|
|
{
|
|
id = path.Substring(subLength + 1, path.Length - subLength - 1),
|
|
parent = parentPath != null ? sessionCache[parentPath] : sessionCache[""],
|
|
duration = 2f,
|
|
validState = currValidState,
|
|
sessionCache = ret
|
|
};
|
|
sessionCacheIndex.Add(path);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
private readonly List<int> removeKeyIndex = new List<int>();
|
|
|
|
private void SessionCacheUpdate()
|
|
{
|
|
currValidState = !currValidState;
|
|
var cacheCount = sessionCacheIndex.Count;
|
|
for (var i = 0; i < cacheCount; i++)
|
|
{
|
|
var c = sessionCacheIndex[i];
|
|
var cache = sessionCache[c];
|
|
var newValue = cache.sessionCache; // 현재값
|
|
|
|
var update = false;
|
|
if (cache.parent != null)
|
|
{
|
|
if (cache.parent.sessionCache.HasChild(cache.id))
|
|
{
|
|
newValue = cache.parent.sessionCache[cache.id];
|
|
}
|
|
else if (cache.sessionCache) // 자식이 없고 캐시는 있는경우 삭제를 위해 // 아이디가 null이라면 삭제된거라 다시 댕글 건다 // 근데 아이디는 상관없음
|
|
{
|
|
newValue = false;
|
|
}
|
|
|
|
update |= cache.parent.isChanged;
|
|
update |= cache.parent.sessionCache.IsModified(cache.id);
|
|
}
|
|
else
|
|
{
|
|
if (string.IsNullOrEmpty(c))
|
|
{
|
|
newValue = Session;
|
|
}
|
|
else
|
|
{
|
|
if (Session.HasChild(c))
|
|
{
|
|
newValue = Session[c];
|
|
}
|
|
else if (cache.sessionCache) // 자식이 없고 캐시는 있는경우 삭제를 위해
|
|
{
|
|
newValue = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
cache.validState = currValidState;
|
|
// 상위가 변경되거나/업데이트틱이 지났다면 캐시 변경
|
|
// 삭제/변경되었다면 캐시가 바뀜
|
|
// 추가/변경 되었다면 뉴가 바뀜
|
|
|
|
update |= SLEntity.Changed(cache.sessionCache, newValue);
|
|
|
|
if (update)
|
|
{
|
|
cache.isChanged = true;
|
|
|
|
cache.sessionCache = newValue;
|
|
|
|
if (cache.sessionHandlers != null)
|
|
{
|
|
foreach (var handler in cache.sessionHandlers)
|
|
{
|
|
var handle = sessionHandlers[handler];
|
|
if (runHandlers.ContainsKey(handle) == false) runHandlers[handle] = handler;
|
|
}
|
|
}
|
|
}
|
|
else if (cache.sessionCache.IsModified(null)) // 추가 삭제인경우 아래로 전파하진 않는다
|
|
{
|
|
cache.sessionCache = newValue;
|
|
if (cache.sessionHandlers != null)
|
|
{
|
|
foreach (var handler in cache.sessionHandlers)
|
|
{
|
|
var handle = sessionHandlers[handler];
|
|
if (runHandlers.ContainsKey(handle) == false) runHandlers[handle] = handler;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cache.sessionHandlers == null || cache.sessionHandlers.Count == 0)
|
|
{
|
|
if (cache.duration < 0)
|
|
{
|
|
removeKeyIndex.Add(i);
|
|
}
|
|
else
|
|
{
|
|
cache.duration -= Time.unscaledDeltaTime;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 부모의 유지시간 갱신
|
|
var parent = cache.parent;
|
|
while (parent != null)
|
|
{
|
|
parent.duration = parent.duration > 2f ? parent.duration : 2f;
|
|
parent = parent.parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < cacheCount; i++)
|
|
{
|
|
var c = sessionCacheIndex[i];
|
|
var cache = sessionCache[c];
|
|
cache.sessionCache.EndModified();
|
|
cache.isChanged = false;
|
|
}
|
|
|
|
for (var i = removeKeyIndex.Count - 1; i >= 0; i--)
|
|
{
|
|
var key = sessionCacheIndex[removeKeyIndex[i]];
|
|
var duration = sessionCache[key].duration;
|
|
if (duration <= 0)
|
|
{
|
|
sessionCache.Remove(key);
|
|
sessionCacheIndex.RemoveAt(removeKeyIndex[i]);
|
|
}
|
|
}
|
|
|
|
removeKeyIndex.Clear();
|
|
|
|
while (runHandlers.Count != 0)
|
|
{
|
|
var handler = runHandlers.Values[0];
|
|
runHandlers.RemoveAt(0);
|
|
handler.Invoke();
|
|
}
|
|
}
|
|
|
|
public static void AddNotify(string sessionPath, Action onChange)
|
|
{
|
|
SessionGet(sessionPath);
|
|
|
|
if (sessionCache[sessionPath].sessionHandlers == null)
|
|
{
|
|
sessionCache[sessionPath].sessionHandlers = new List<Action>();
|
|
}
|
|
|
|
sessionCache[sessionPath].sessionHandlers.Add(onChange);
|
|
|
|
if (sessionHandlers.ContainsKey(onChange) == false)
|
|
{
|
|
sessionHandlers[onChange] = handlerIndex;
|
|
handlerIndex += 1;
|
|
sessionCounters[onChange] = 0;
|
|
}
|
|
|
|
sessionCounters[onChange] += 1;
|
|
|
|
var handlerId = sessionHandlers[onChange];
|
|
|
|
if (runHandlers.ContainsKey(handlerId) == false)
|
|
{
|
|
runHandlers[handlerId] = onChange;
|
|
}
|
|
|
|
if (updateSessionCache == false) // 코루틴 등에서 첫프레임 실행 보장
|
|
{
|
|
while (runHandlers.Count != 0)
|
|
{
|
|
var handler = runHandlers.Values[0];
|
|
runHandlers.RemoveAt(0);
|
|
handler.Invoke();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void RemoveNotify(string sessionPath, Action onChange)
|
|
{
|
|
sessionCounters[onChange] -= 1;
|
|
if (sessionCounters[onChange] == 0)
|
|
{
|
|
sessionHandlers.Remove(onChange);
|
|
sessionCounters.Remove(onChange);
|
|
}
|
|
|
|
if (sessionCache.TryGetValue(sessionPath, out var cache))
|
|
{
|
|
cache.sessionHandlers.Remove(onChange);
|
|
}
|
|
else
|
|
{
|
|
SLLog.Error($"Cannot RemoveNotify : {sessionPath}", onChange.Target);
|
|
}
|
|
}
|
|
|
|
public SLGame(string[] managers, string main)
|
|
{
|
|
var sessionRoot = SLEntity.Empty;
|
|
sessionRoot["Session"]["Data"] = SLSystem.Data;
|
|
Session = sessionRoot["Session"];
|
|
|
|
Camera = Camera.main;
|
|
|
|
sessionCache[""] = new SessionCache
|
|
{
|
|
id = "",
|
|
parent = null,
|
|
duration = float.PositiveInfinity,
|
|
validState = currValidState,
|
|
sessionCache = Session
|
|
};
|
|
sessionCacheIndex.Add("");
|
|
|
|
gameComponents = new Dictionary<string, SLGameComponent>();
|
|
commandQueue = new List<(string command, SLEntity entity)>();
|
|
activeComponents = new List<SLGameComponent>();
|
|
readyActiveComponents = new List<SLGameComponent>();
|
|
gameComponents.CreateInstanceDictionary(managers);
|
|
commands = new Dictionary<string, (SLGameComponent manager, MethodInfo method)>();
|
|
foreach (var component in gameComponents)
|
|
{
|
|
commands.CreateCommandInfoToBaseType(component.Value, typeof(SLEntity));
|
|
}
|
|
|
|
if (main == string.Empty)
|
|
{
|
|
main = managers[0];
|
|
}
|
|
|
|
ActiveComponent(main, true);
|
|
}
|
|
|
|
internal void Update()
|
|
{
|
|
Session["Time"] += 1;
|
|
|
|
foreach (var component in readyActiveComponents)
|
|
{
|
|
activeComponents.Add(component);
|
|
}
|
|
|
|
readyActiveComponents.Clear();
|
|
|
|
canCommand = true;
|
|
updateSessionCache = true;
|
|
|
|
foreach (var command in commandQueue)
|
|
{
|
|
#if UNITY_EDITOR
|
|
try
|
|
{
|
|
#endif
|
|
Command(command.command, command.entity);
|
|
#if UNITY_EDITOR
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
SLLog.Error($"Can't Run [{command.command}]. {e.Message} {e.StackTrace}");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
foreach (var component in activeComponents)
|
|
{
|
|
#if UNITY_EDITOR
|
|
try
|
|
{
|
|
#endif
|
|
component.Update();
|
|
#if UNITY_EDITOR
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
SLLog.Error($"Can't Update [{component.GetType().Name}]. {e.Message} {e.StackTrace}");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
commandQueue.Clear();
|
|
|
|
activeComponents.RemoveAll(c =>
|
|
{
|
|
if (c.Active == false)
|
|
{
|
|
#if UNITY_EDITOR
|
|
try
|
|
{
|
|
#endif
|
|
c.End();
|
|
#if UNITY_EDITOR
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
SLLog.Error($"Can't Update [{c.GetType().Name}]. {e.Message} {e.StackTrace}");
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
canCommand = false;
|
|
|
|
SessionCacheUpdate();
|
|
|
|
updateSessionCache = false;
|
|
}
|
|
|
|
public static void ActiveComponent(string component, bool active)
|
|
{
|
|
// TODO: 중복 추가등 체크필요가 있을까?
|
|
var instance = gameComponents[component];
|
|
if (active)
|
|
{
|
|
if (instance.Active == false)
|
|
{
|
|
if (activeComponents.Contains(instance)) // 이번 프레임에 삭제된 경우
|
|
{
|
|
instance.End();
|
|
}
|
|
else
|
|
{
|
|
readyActiveComponents.Add(instance);
|
|
}
|
|
instance.Active = true;
|
|
instance.Begin();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
instance.Active = false;
|
|
}
|
|
}
|
|
|
|
public static void ActiveComponent<T>(bool active) where T : SLGameComponent
|
|
{
|
|
ActiveComponent(typeof(T).Name, active);
|
|
}
|
|
|
|
public static void Command(string commandName, SLEntity entity)
|
|
{
|
|
#if UNITY_EDITOR
|
|
try
|
|
{
|
|
#endif
|
|
if (commands.ContainsKey(commandName) == false)
|
|
{
|
|
SLLog.Error($"Can't Run [{commandName}]");
|
|
return;
|
|
}
|
|
|
|
if (canCommand == false)
|
|
{
|
|
commandQueue.Add((commandName, entity));
|
|
return;
|
|
}
|
|
|
|
var (component, method) = commands[commandName];
|
|
|
|
if (component.Active == false)
|
|
{
|
|
SLLog.Error($"Can't Run [{commandName}]. Component is not active");
|
|
return;
|
|
}
|
|
method.Invoke(component, new[] { entity });
|
|
#if UNITY_EDITOR
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
var message = $"Can't Run [{commandName}]. \n{e.Message} \n{e.StackTrace}";
|
|
var inner = e.InnerException;
|
|
while (inner != null)
|
|
{
|
|
message += $"\n{inner.Message} \n{inner.StackTrace}";
|
|
inner = inner.InnerException;
|
|
}
|
|
SLLog.Error(message);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public static void Event(string eventName, SLEntity context)
|
|
{
|
|
if (context == false)
|
|
{
|
|
context = SLEntity.Empty;
|
|
context["EventName"] = eventName;
|
|
}
|
|
var id = Session["Events"].Get(eventName).Count();
|
|
context = context.Override();
|
|
Session["Events"].Set($"{eventName}.{id}", context);
|
|
}
|
|
|
|
public static void ClearEvent()
|
|
{
|
|
Session["Events"] = false;
|
|
}
|
|
} |