using ProjectM.Simulation; using Unity.Burst; using Unity.Entities; using Unity.NetCode; namespace ProjectM.Server { /// /// Server-authoritative expiry of TIMED s. Each tick it walks every entity's /// server-only buffer; for any row whose has /// elapsed (wrap-safe compare, never raw uint<) it removes /// the matching StatModifier(s) by SourceId and the timed row. The shortened StatModifier [GhostField] buffer /// auto-replicates (OwnerSendType.All), so StatRecomputeSystem reverts the effective stat on both worlds with no /// change. Runs in the plain server (NOT the predicted loop) so it is applied /// exactly once and never double-removed on rollback; a DynamicBuffer mutation is not a structural change, so it /// is safe to mutate the iterated entity's own buffers in place. /// [BurstCompile] [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)] [UpdateInGroup(typeof(SimulationSystemGroup))] public partial struct TimedModifierExpirySystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate(); state.RequireForUpdate(state.GetEntityQuery(ComponentType.ReadOnly())); } [BurstCompile] public void OnUpdate(ref SystemState state) { var serverTick = SystemAPI.GetSingleton().ServerTick; if (!serverTick.IsValid) return; foreach (var (timed, mods) in SystemAPI.Query, DynamicBuffer>()) { for (int i = timed.Length - 1; i >= 0; i--) { uint until = timed[i].UntilTick; if (until == 0) continue; // inert (no expiry scheduled) var untilTick = new NetworkTick(until); if (untilTick.IsValid && untilTick.IsNewerThan(serverTick)) continue; // not yet due TimedModifierUtil.RemoveBySourceId(mods, timed[i].SourceId); timed.RemoveAtSwapBack(i); } } } } }