543 lines
13 KiB
C#
543 lines
13 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using AsyncStateMachine;
|
|
using AsyncStateMachine.Behaviours;
|
|
using AsyncStateMachine.Callbacks;
|
|
using AsyncStateMachine.Contracts;
|
|
|
|
|
|
|
|
|
|
namespace ServerCore;
|
|
|
|
//=============================================================================================
|
|
// Hierachical Finite State Machines
|
|
//=============================================================================================
|
|
|
|
public abstract class HFSMBase<TTrigger, TState>
|
|
where TTrigger : struct
|
|
where TState : struct
|
|
{
|
|
private StateMachineConfiguration<TTrigger, TState>? m_sm_config;
|
|
private StateMachine<TTrigger, TState>? m_sm;
|
|
|
|
public delegate bool CB_Init(HFSMBase<TTrigger, TState> initHFSM);
|
|
public delegate void CB_Enter();
|
|
public delegate void CB_Tick(HFSMBase<TTrigger, TState> initHFSM);
|
|
public delegate void CB_Exit();
|
|
public delegate void CB_Transition(HFSMBase<TTrigger, TState> initHFSM);
|
|
|
|
private CB_Init? m_cbInit;
|
|
private CB_Tick? m_cb_update_nullable;
|
|
private readonly ConcurrentDictionary<TState, Action> m_update_handlers = new();
|
|
private readonly ConcurrentDictionary<TState, CB_Transition> m_transition_handlers = new();
|
|
private readonly SimpleTimer m_state_timer = new();
|
|
|
|
private uint m_hfsm_id = 0;
|
|
|
|
private bool m_is_disable = false;
|
|
|
|
private bool m_is_paused = false;
|
|
|
|
internal class HFSMInstantId : InstantIdGeneratorWith32Bit { }
|
|
|
|
public HFSMBase()
|
|
{ }
|
|
|
|
private bool createStateMachine(TState rootState)
|
|
{
|
|
try
|
|
{
|
|
m_sm_config = new StateMachineConfiguration<TTrigger, TState>(rootState);
|
|
m_sm = new StateMachine<TTrigger, TState>(m_sm_config);
|
|
m_hfsm_id = Singleton<HFSMInstantId>.It.nextId();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
var err_msg = $"Failed to create StateMachine !!! : Exception:{e}";
|
|
Log.getLogger().fatal(err_msg);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> initHfsm(TState root, CB_Init cb_init)
|
|
{
|
|
var is_success = createStateMachine(root);
|
|
if (false == is_success)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_cbInit = cb_init;
|
|
|
|
is_success = m_cbInit(this);
|
|
if (false == is_success)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
onSubscribeStateChangeNotify();
|
|
|
|
if (m_sm == null)
|
|
return false;
|
|
|
|
await m_sm.InitializeAsync();
|
|
|
|
return true;
|
|
}
|
|
|
|
public virtual void onSubscribeStateChangeNotify() { }
|
|
|
|
public StateMachineConfiguration<TTrigger, TState>? getStateMachineConfiguration() => m_sm_config;
|
|
|
|
public StateMachine<TTrigger, TState>? getStateMachine() => m_sm;
|
|
|
|
public uint getHfsmId() => m_hfsm_id;
|
|
|
|
public SimpleTimer getStateTimer() => m_state_timer;
|
|
|
|
public TState? getState() { return m_sm?.CurrentState; }
|
|
|
|
public bool isState(TState stateType) { return m_sm?.CurrentState.Equals(stateType) ?? false; }
|
|
|
|
public async Task<bool> isInStateAsync(TState stateType)
|
|
{
|
|
if (m_sm == null)
|
|
return false;
|
|
|
|
return await m_sm.InStateAsync(stateType);
|
|
}
|
|
|
|
public void setDisable(bool isDisable)
|
|
{
|
|
m_is_disable = isDisable;
|
|
}
|
|
|
|
public bool isDisable() => m_is_disable;
|
|
|
|
public void bindUpdate(CB_Tick cb)
|
|
{
|
|
m_cb_update_nullable = cb;
|
|
}
|
|
|
|
private void doUpdate()
|
|
{
|
|
m_cb_update_nullable?.Invoke(this);
|
|
}
|
|
|
|
public bool onUpdateHFSM(StateMachine<TTrigger, TState> stateManchine, Action fnUpdate)
|
|
{
|
|
var curr_state = stateManchine.CurrentState;
|
|
if (curr_state == null)
|
|
return false;
|
|
|
|
if (true == m_update_handlers.ContainsKey((TState)curr_state))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (false == m_update_handlers.TryAdd((TState)curr_state, fnUpdate))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void doCurrentUpdate()
|
|
{
|
|
var curr_state = getState();
|
|
if (curr_state == null)
|
|
return;
|
|
|
|
if (true != m_update_handlers.TryGetValue((TState)curr_state, out var update_handler))
|
|
{
|
|
return;
|
|
}
|
|
|
|
update_handler?.Invoke();
|
|
}
|
|
|
|
public async Task fireAsyncTo(TTrigger trigger)
|
|
{
|
|
if (true == isDisable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_sm == null)
|
|
return;
|
|
|
|
await m_sm.FireAsync(trigger);
|
|
}
|
|
|
|
public void startStateTimer(int msec)
|
|
{
|
|
m_state_timer.activate(msec);
|
|
}
|
|
|
|
public void stopStateTimer()
|
|
{
|
|
m_state_timer.deactivate();
|
|
}
|
|
|
|
public void resetStateTimer()
|
|
{
|
|
m_state_timer.reset();
|
|
}
|
|
|
|
public void pauseStateTimer()
|
|
{
|
|
m_state_timer.pause();
|
|
}
|
|
|
|
public void unpauseStateTimer()
|
|
{
|
|
m_state_timer.unpause();
|
|
}
|
|
|
|
public int getReaminTimeMSec()
|
|
{
|
|
return (int)m_state_timer.getRemainTimeMSec();
|
|
}
|
|
|
|
public bool isExpiredStateTimer()
|
|
{
|
|
if (m_state_timer.expired())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public virtual void onPause()
|
|
{
|
|
m_is_paused = true;
|
|
|
|
pauseStateTimer();
|
|
}
|
|
|
|
public virtual void onUnpause()
|
|
{
|
|
m_is_paused = false;
|
|
|
|
unpauseStateTimer();
|
|
}
|
|
|
|
public bool isPausing()
|
|
{
|
|
return m_is_paused;
|
|
}
|
|
|
|
public virtual void onTick()
|
|
{
|
|
if (true == isDisable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (true == isPausing())
|
|
{
|
|
return;
|
|
}
|
|
|
|
doCurrentUpdate();
|
|
doUpdate();
|
|
}
|
|
|
|
}//HFSMBase
|
|
|
|
//=============================================================================================
|
|
/// Compound HFSM
|
|
//=============================================================================================
|
|
public class CompoundHFSM<HFSMType, TTrigger, TState>
|
|
where HFSMType : notnull
|
|
where TTrigger : struct
|
|
where TState : struct
|
|
{
|
|
private readonly ConcurrentDictionary<HFSMType, ConcurrentBag<HFSMBase<TTrigger, TState>>> m_hfsm_groups = new();
|
|
|
|
private class PriorityUpdate
|
|
{
|
|
public HFSMType HFSMTypeValue { get; set; }
|
|
public int SeqNo { get; set; }
|
|
|
|
public PriorityUpdate(HFSMType hFSMTypeValue, int seqNo)
|
|
{
|
|
HFSMTypeValue = hFSMTypeValue;
|
|
SeqNo = seqNo;
|
|
}
|
|
}
|
|
|
|
protected ConcurrentDictionary<HFSMType, int>? m_priority_hfsm_updates_nullable;
|
|
private readonly ConcurrentBag<ConcurrentBag<HFSMBase<TTrigger, TState>>> m_update_hfsms = new();
|
|
|
|
|
|
public CompoundHFSM()
|
|
{
|
|
}
|
|
|
|
public async Task<HFSMBase<TTrigger, TState>?> createHfsm<THFSM>( TState rootState
|
|
, HFSMBase<TTrigger, TState>.CB_Init cbInitHFSM)
|
|
where THFSM : HFSMBase<TTrigger, TState>, new()
|
|
{
|
|
var new_hfsm = new THFSM();
|
|
if (true != await new_hfsm.initHfsm(rootState, cbInitHFSM))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new_hfsm;
|
|
}
|
|
|
|
public bool addHFSM(HFSMType hfsmType, HFSMBase<TTrigger, TState> stateMachine)
|
|
{
|
|
if (null == stateMachine)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var hfsm_list = findHFSMs(hfsmType);
|
|
if (hfsm_list == null)
|
|
{
|
|
hfsm_list = new ConcurrentBag<HFSMBase<TTrigger, TState>>();
|
|
m_hfsm_groups.TryAdd(hfsmType, hfsm_list);
|
|
}
|
|
|
|
hfsm_list.Add(stateMachine);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool removeHFSM(HFSMType hfsmType, uint hfsmId)
|
|
{
|
|
var hfsms = findHFSMs(hfsmType);
|
|
if (hfsms == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var hfsm in hfsms)
|
|
{
|
|
if (hfsm.getHfsmId() == hfsmId)
|
|
{
|
|
hfsms.TryTake(out _);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool removeHFSM(HFSMType hfsmType, HFSMBase<TTrigger, TState> stateMachine)
|
|
{
|
|
if (null == stateMachine)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return removeHFSM(hfsmType, stateMachine.getHfsmId());
|
|
}
|
|
|
|
public bool removeHFSMBy(HFSMType hfsmType, out ConcurrentBag<HFSMBase<TTrigger, TState>>? removeHFSMs)
|
|
{
|
|
removeHFSMs = null;
|
|
if (false == m_hfsm_groups.TryRemove(hfsmType, out var removed_hfsms))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
removeHFSMs = removed_hfsms;
|
|
return true;
|
|
}
|
|
|
|
public HFSMBase<TTrigger, TState>? lookupHFSM(HFSMType hfsmType, int hfsmId)
|
|
{
|
|
var hfsms = findHFSMs(hfsmType);
|
|
if (hfsms == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var found_hfsm = hfsms.First(delegate (HFSMBase<TTrigger, TState> hfsm)
|
|
{
|
|
return hfsm.getHfsmId() == hfsmId;
|
|
});
|
|
if (null == found_hfsm)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return found_hfsm;
|
|
}
|
|
|
|
public void setPriorityUpdates(ConcurrentDictionary<HFSMType, int> prioritys)
|
|
{
|
|
m_priority_hfsm_updates_nullable = prioritys;
|
|
}
|
|
|
|
public void resetUpdateHFSMs()
|
|
{
|
|
m_update_hfsms.Clear();
|
|
}
|
|
|
|
public bool getCurrentState(HFSMType hfsmType, int hfsmId, out TState? state)
|
|
{
|
|
state = default;
|
|
|
|
var hfsms = findHFSMs(hfsmType);
|
|
if (hfsms == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var hfsm in hfsms)
|
|
{
|
|
if (hfsmId == hfsm.getHfsmId())
|
|
{
|
|
state = hfsm.getState();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public async Task<bool> isCurrentState(HFSMType hfsmType, TState state)
|
|
{
|
|
var hfsms = findHFSMs(hfsmType);
|
|
if (hfsms == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var hfsm in hfsms)
|
|
{
|
|
if (true == await hfsm.isInStateAsync(state))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public async Task fireAsyncTo(HFSMType hfsmType, int hfsmId, TTrigger trigger)
|
|
{
|
|
var hfsms = findHFSMs(hfsmType);
|
|
if (hfsms == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var hfsm in hfsms)
|
|
{
|
|
if (hfsmId == hfsm.getHfsmId())
|
|
{
|
|
await hfsm.fireAsyncTo(trigger);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task fireToGroup(HFSMType hfsmType, TTrigger trigger)
|
|
{
|
|
var hfsms = findHFSMs(hfsmType);
|
|
if (hfsms == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var hfsm in hfsms)
|
|
{
|
|
await hfsm.fireAsyncTo(trigger);
|
|
}
|
|
}
|
|
|
|
public async Task fireToAll(TTrigger trigger)
|
|
{
|
|
foreach (var pair_hfsm_group in m_hfsm_groups)
|
|
{
|
|
foreach (var fsm in pair_hfsm_group.Value)
|
|
{
|
|
await fsm.fireAsyncTo(trigger);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void onTick()
|
|
{
|
|
if (0 == m_update_hfsms.Count)
|
|
{
|
|
if (null != m_priority_hfsm_updates_nullable)
|
|
{
|
|
//우선 순위가 설정된 경우, 우선 순위값을 list 로 만든다.
|
|
var toSortList = new List<PriorityUpdate>();
|
|
foreach (var pair_priority_info in m_priority_hfsm_updates_nullable)
|
|
{
|
|
var priorityUpdate = new PriorityUpdate(pair_priority_info.Key, pair_priority_info.Value);
|
|
toSortList.Add(priorityUpdate);
|
|
}
|
|
|
|
//우선 순위값을 비교하여 정렬 한다.
|
|
toSortList.Sort(delegate (PriorityUpdate a, PriorityUpdate b)
|
|
{
|
|
if (a.SeqNo == b.SeqNo)
|
|
{
|
|
return 0;
|
|
}
|
|
if (a.SeqNo > b.SeqNo)
|
|
{
|
|
return 1;
|
|
}
|
|
return -1;
|
|
});
|
|
|
|
//정렬된 순서대로 HFSM Group 을 추가 한다.
|
|
foreach (var value in toSortList)
|
|
{
|
|
var hfsms = findHFSMs(value.HFSMTypeValue);
|
|
if (hfsms != null)
|
|
{
|
|
m_update_hfsms.Add(hfsms);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//우선 순위가 설정되지 않은 경우
|
|
foreach (var hfsm_group in m_hfsm_groups)
|
|
{
|
|
m_update_hfsms.Add(hfsm_group.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//등록된 순서대로 HFSM Group 을 갱신 한다.
|
|
foreach (var hfsm_list in m_update_hfsms)
|
|
{
|
|
foreach (var hfsm in hfsm_list)
|
|
{
|
|
hfsm.onTick();
|
|
}
|
|
}
|
|
}
|
|
|
|
private ConcurrentBag<HFSMBase<TTrigger, TState>>? findHFSMs(HFSMType fsmType)
|
|
{
|
|
m_hfsm_groups.TryGetValue(fsmType, out var found_group);
|
|
return found_group;
|
|
}
|
|
|
|
}//CompoundHFSM
|
|
//ServerCore
|