using System; using System.Collections; using System.Data; using System.Threading; using BlueWest.Collections; using BlueWest.Coroutines; using BlueWest.Tools; namespace BlueWest.Core.ComponentSystem { public enum ArtefactFrequency { T120Hz, T60Hz, T30Hz, T20Hz, T10Hz, T4Hz , T3Hz , T2Hz , T1Hz } public class Artefact { public Artefact() { _autoTickTimer = new TimerTick(); _autoTickTimer.Reset(); _maximumElapsedTime = TimeSpan.FromMilliseconds(500.0); } // Time Management private readonly TimeSpan _maximumElapsedTime; private TimeSpan _accumulatedElapsedGameTime; private TimeSpan TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 30); // target elapsed time is by default 250Hz private readonly TimerTick _autoTickTimer; private TimeSpan accumulatedElapsedGameTime; private FastDictionary behaviorConfigType = new FastDictionary(10); private readonly FastList _coroutines = new FastList(10000); private bool? _prevVisibility; // Mini ECS protected EventManager _eventManager; private bool _startCalled; internal VisibilityHandler _visibilityHandler = new VisibilityHandler(); internal FastList _components = new FastList(10); private int _componentsCount = 0; private readonly ArtefactFrequency BehaviorType; // OLD STUFF protected internal ArtefactFrequency Frequency; protected internal bool IsActive { get { return _visibilityHandler.IsVisible; } set { SetActive(value); } } /// /// Initial setup of the entity. Triggers Awake method if the node is visible /// and triggers an visibility change event. /// public void SetupEntity(EventManager eventManager) { _eventManager = eventManager; switch (BehaviorType) { case ArtefactFrequency.T1Hz: TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 1); break; case ArtefactFrequency.T2Hz: TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 2); break; case ArtefactFrequency.T3Hz: TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 3); break; case ArtefactFrequency.T4Hz: TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 4); break; case ArtefactFrequency.T10Hz: TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 10); break; case ArtefactFrequency.T20Hz: TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 20); break; case ArtefactFrequency.T30Hz: TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 30); break; case ArtefactFrequency.T60Hz: TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 60); break; case ArtefactFrequency.T120Hz: TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 120); break; default: throw new EvaluateException( $"BehaviorType of class can't be null. Please assign it in the Constructor."); } if (!_visibilityHandler.IsVisible) return; Awake(); OnVisibilityChange(); if (!_visibilityHandler.IsVisible) return; if (!_startCalled) { _startCalled = true; Start(); } } public void AddComponent() where T : Component { var component = Activator.CreateInstance(typeof(T), _eventManager) as Component; _components.Add(component); _componentsCount += 1; component.Start(); } protected virtual void Awake() { } protected virtual void Start() { } public void ProcessComponents(double delta) { for (int i = 0; i < _components.Length; i++) { var component = _components.Buffer[i]; component.EveryFrame(delta); component.HandleCoroutines(); } } public void StartCoroutine(IEnumerator routine) { _coroutines.Add(routine); } public bool LoopEnabled = true; /// /// Called every frame /// public void RunLoop() { RetryTick: if (!LoopEnabled) return; if (!_visibilityHandler.IsVisible) return; _autoTickTimer.Tick(); var elapsedAdjustedTime = _autoTickTimer.ElapsedTimeWithPause; int updateCount = 1; // then make ElapsedAdjustedTime = TargetElapsedTime. We take the same internal rules as XNA if (Math.Abs(elapsedAdjustedTime.Ticks - TargetElapsedTime.Ticks) < (TargetElapsedTime.Ticks >> 6)) { elapsedAdjustedTime = TargetElapsedTime; } // Update the accumulated time accumulatedElapsedGameTime += elapsedAdjustedTime; if (accumulatedElapsedGameTime < TargetElapsedTime) { var sleepTime = (TargetElapsedTime - accumulatedElapsedGameTime).TotalMilliseconds; if (sleepTime >= 2.0) { System.Threading.Thread.Sleep(1); } goto RetryTick; } // We are going to call Update updateCount times, so we can subtract this from accumulated elapsed game time accumulatedElapsedGameTime = new TimeSpan(accumulatedElapsedGameTime.Ticks - (updateCount * TargetElapsedTime.Ticks)); for (int i = 0; i < updateCount; i++) { double delta = TargetElapsedTime.TotalSeconds; Update(delta); ProcessComponents(delta); } if (!_visibilityHandler.IsVisible) return; // Added to avoid running after deactivating node HandleCoroutines(); goto RetryTick; } protected virtual void Update(double delta) { } public void HandleCoroutines() { for (var i = 0; i < _coroutines.Length; i++) { var yielded = _coroutines.Buffer[i].Current is CustomYieldInstruction yielder && yielder.MoveNext(); if (yielded || _coroutines.Buffer[i].MoveNext()) continue; _coroutines.RemoveAt(i); i--; } } /// /// Trigger OnEnable and OnDisable methods regarding the current and previous visibility. /// internal void OnVisibilityChange() { var curVisibility = _visibilityHandler.IsVisible; if (curVisibility == _prevVisibility) return; if (curVisibility) OnEnable(); if (!curVisibility) OnDisable(); _prevVisibility = curVisibility; } protected virtual void OnEnable() { } protected virtual void OnDisable() { } public void SetActive(bool status) { _visibilityHandler.SetVisibility(status); OnVisibilityChange(); } } }