the files
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
using System.Collections;
|
||||
using CampusNet.Protocol;
|
||||
|
||||
namespace CampusNet.Services;
|
||||
|
||||
public sealed class Actor
|
||||
{
|
||||
public int Nr;
|
||||
public string UserId = "";
|
||||
public Connection? Conn; // null while inactive (disconnected with PlayerTtl)
|
||||
public readonly Hashtable Props = new();
|
||||
public bool Inactive;
|
||||
public DateTime InactiveSince;
|
||||
public readonly HashSet<byte> Groups = new(); // interest groups (0 = always)
|
||||
public readonly List<CachedEvent> OwnCache = new(); // MergeCache / ReplaceCache per actor (legacy)
|
||||
|
||||
// Traffic counters for the periodic room summary (see GameService.Sweep): what this actor
|
||||
// raised, how much of it was unreliable, per event code, and how many recipients it reached.
|
||||
public long Raised, RaisedUnreliable, Delivered, DroppedNoRecipient;
|
||||
public readonly Dictionary<byte, int> RaisedByCode = new();
|
||||
public readonly Dictionary<byte, int> RaisedByGroup = new();
|
||||
}
|
||||
|
||||
public sealed class CachedEvent
|
||||
{
|
||||
public int ActorNr; public byte Code; public object? Data; public byte Group;
|
||||
}
|
||||
|
||||
/// <summary>A game room: actors, properties, and the event cache PUN relies on for late joiners.</summary>
|
||||
public sealed class Room
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly Hashtable Props = new(); // byte system keys + custom keys
|
||||
public readonly SortedDictionary<int, Actor> Actors = new();
|
||||
public readonly List<CachedEvent> Cache = new(); // AddToRoomCache / AddToRoomCacheGlobal, in order
|
||||
public int NextActorNr = 1;
|
||||
public int PlayerTtl, EmptyRoomTtl;
|
||||
public DateTime EmptySince = DateTime.UtcNow;
|
||||
public readonly object Sync = new();
|
||||
public readonly DateTime CreatedAt = DateTime.UtcNow;
|
||||
|
||||
public Room(string name) { Name = name; }
|
||||
|
||||
public int MaxPlayers => Props[GameProp.MaxPlayers] is byte b ? b : (Props[GameProp.MaxPlayers] is int i ? i : 0);
|
||||
public bool IsOpen => Props[GameProp.IsOpen] is bool o ? o : true;
|
||||
public bool IsVisible => Props[GameProp.IsVisible] is bool v ? v : true;
|
||||
public int ActiveCount => Actors.Values.Count(a => !a.Inactive);
|
||||
public int MasterClientId => Actors.Values.Where(a => !a.Inactive).Select(a => a.Nr).DefaultIfEmpty(0).Min();
|
||||
|
||||
public Actor? FindByUserId(string userId) => Actors.Values.FirstOrDefault(a => a.UserId == userId);
|
||||
|
||||
/// <summary>Game properties as sent to clients: stored props plus the live system values.</summary>
|
||||
public Hashtable GamePropsForClient()
|
||||
{
|
||||
var h = new Hashtable();
|
||||
foreach (DictionaryEntry e in Props) h[e.Key] = e.Value;
|
||||
h[GameProp.MasterClientId] = MasterClientId;
|
||||
h[GameProp.PlayerCount] = (byte)ActiveCount;
|
||||
if (!h.ContainsKey(GameProp.IsOpen)) h[GameProp.IsOpen] = true;
|
||||
if (!h.ContainsKey(GameProp.IsVisible)) h[GameProp.IsVisible] = true;
|
||||
return h;
|
||||
}
|
||||
|
||||
/// <summary>All players' properties keyed by actor number (what the join response carries).</summary>
|
||||
public Hashtable AllPlayerProps()
|
||||
{
|
||||
var h = new Hashtable();
|
||||
foreach (var a in Actors.Values) h[a.Nr] = PlayerPropsForClient(a);
|
||||
return h;
|
||||
}
|
||||
|
||||
public static Hashtable PlayerPropsForClient(Actor a)
|
||||
{
|
||||
var h = new Hashtable();
|
||||
foreach (DictionaryEntry e in a.Props) h[e.Key] = e.Value;
|
||||
h[PlayerProp.UserId] = a.UserId;
|
||||
h[PlayerProp.IsInactive] = a.Inactive;
|
||||
if (!h.ContainsKey(PlayerProp.NickName)) h[PlayerProp.NickName] = "";
|
||||
return h;
|
||||
}
|
||||
|
||||
public int[] ActorList() => Actors.Values.Where(a => !a.Inactive).Select(a => a.Nr).ToArray();
|
||||
|
||||
/// <summary>Copy props from a client hashtable into a store, honouring the "null removes" rule; returns the changed subset.</summary>
|
||||
public static Hashtable Merge(Hashtable target, Hashtable incoming)
|
||||
{
|
||||
var changed = new Hashtable();
|
||||
foreach (DictionaryEntry e in incoming)
|
||||
{
|
||||
if (e.Value == null) { if (target.ContainsKey(e.Key)) { target.Remove(e.Key); changed[e.Key] = null; } }
|
||||
else { target[e.Key] = e.Value; changed[e.Key] = e.Value; }
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>Check-and-set: every expected value must match the current one (both absent counts as match).</summary>
|
||||
public static bool CasMatches(Hashtable target, Hashtable? expected)
|
||||
{
|
||||
if (expected == null) return true;
|
||||
foreach (DictionaryEntry e in expected)
|
||||
{
|
||||
var cur = target[e.Key];
|
||||
if (!ValuesEqual(cur, e.Value)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValuesEqual(object? a, object? b)
|
||||
{
|
||||
if (a == null || b == null) return a == null && b == null;
|
||||
if (a is byte[] ab && b is byte[] bb) return ab.AsSpan().SequenceEqual(bb);
|
||||
if (a is Array aa && b is Array ba)
|
||||
{
|
||||
if (aa.Length != ba.Length) return false;
|
||||
for (int i = 0; i < aa.Length; i++) if (!ValuesEqual(aa.GetValue(i), ba.GetValue(i))) return false;
|
||||
return true;
|
||||
}
|
||||
if (a is CustomValue ca && b is CustomValue cb) return ca.Code == cb.Code && ca.Data.AsSpan().SequenceEqual(cb.Data);
|
||||
try { return Convert.ToDouble(a) == Convert.ToDouble(b); } catch { }
|
||||
return a.Equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Every room this process hosts (master and game share it - one process).</summary>
|
||||
public static class Rooms
|
||||
{
|
||||
private static readonly Dictionary<string, Room> _rooms = new();
|
||||
public static readonly object Sync = new();
|
||||
|
||||
public static Room? Get(string name) { lock (Sync) return _rooms.TryGetValue(name, out var r) ? r : null; }
|
||||
public static Room GetOrCreate(string name, out bool created)
|
||||
{
|
||||
lock (Sync)
|
||||
{
|
||||
if (_rooms.TryGetValue(name, out var r)) { created = false; return r; }
|
||||
r = new Room(name); _rooms[name] = r; created = true; return r;
|
||||
}
|
||||
}
|
||||
public static void Remove(string name) { lock (Sync) _rooms.Remove(name); }
|
||||
public static List<Room> All() { lock (Sync) return _rooms.Values.ToList(); }
|
||||
public static int Count { get { lock (Sync) return _rooms.Count; } }
|
||||
}
|
||||
Reference in New Issue
Block a user