Files
MSCO/WorldVarSync.cs
T

157 lines
5.9 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using HutongGames.PlayMaker;
using UnityEngine;
namespace MSCO
{
/// <summary>
/// Jobs, Aufträge und Laden: deren Fortschritt steht in PlayMaker-FSMs (z.B. "Auftrag angenommen", Anzahl
/// geleerter Klärgruben, bestellte Teile). Übertragen werden nur Ganzzahlen und Schalter - keine Kommazahlen
/// (Timer, Positionen), die jeder selbst weiterzählt. Wer etwas ändert, schickt es; Werte, die ständig hin und her
/// springen (interne Zähler), werden nach kurzer Zeit nicht mehr übertragen.
/// </summary>
public class WorldVarSync
{
/// <summary>Wurzelobjekte mit Jobs/Aufträgen/Laden.</summary>
public static readonly string[] Roots = { "JOBS", "STORE", "REPAIRSHOP", "INSPECTION", "LANDFILL", "DANCEHALL", "COTTAGE", "RALLY" };
const float ScanInterval = 15f;
const float CheckInterval = 0.5f;
const float MinResend = 2f; // höchstens so oft pro Wert
const int FlipLimit = 6; // so viele Änderungen ...
const float FlipWindow = 30f; // ... in dieser Zeit = interner Zähler, nicht mehr übertragen
const int MaxPerMessage = 200;
class Var
{
public int Key;
public NamedVariable Variable;
public byte Type; // 2 int, 3 bool
public int LastI;
public bool LastB;
public float LastSent = -100f;
public float WindowStart;
public int Flips;
public bool Muted;
}
readonly Dictionary<int, Var> vars = new Dictionary<int, Var>();
readonly HashSet<PlayMakerFSM> scanned = new HashSet<PlayMakerFSM>();
readonly List<Var> outgoing = new List<Var>();
float nextScan, nextCheck;
public int Count { get { return vars.Count; } }
public void Reset()
{
vars.Clear();
scanned.Clear();
nextScan = -1f; // erster Suchlauf verteilt, siehe Update
}
public void Update(Action<MsgType, byte[]> send)
{
if (nextScan < 0f) nextScan = Time.time + 3.8f; // nicht alle Sync-Teile im selben Frame suchen lassen
if (Time.time >= nextScan)
{
nextScan = Time.time + ScanInterval;
Scan();
}
if (Time.time < nextCheck) return;
nextCheck = Time.time + CheckInterval;
outgoing.Clear();
foreach (Var v in vars.Values)
{
if (v.Muted || !Changed(v)) continue;
if (Time.time - v.LastSent < MinResend) continue; // später nochmal prüfen
Remember(v);
if (Time.time - v.WindowStart > FlipWindow) { v.WindowStart = Time.time; v.Flips = 0; }
if (++v.Flips > FlipLimit) { v.Muted = true; continue; }
v.LastSent = Time.time;
outgoing.Add(v);
}
for (int start = 0; start < outgoing.Count; start += MaxPerMessage)
{
int from = start, count = Math.Min(MaxPerMessage, outgoing.Count - start);
send(MsgType.WorldVars, NetSession.Build(w =>
{
w.Write(count);
for (int i = from; i < from + count; i++)
{
Var v = outgoing[i];
w.Write(v.Key);
w.Write(v.Type);
if (v.Type == 2) w.Write(v.LastI); else w.Write(v.LastB);
}
}));
}
}
public void Handle(BinaryReader r)
{
int count = r.ReadInt32();
for (int i = 0; i < count; i++)
{
int key = r.ReadInt32();
byte type = r.ReadByte();
int iv = 0; bool bv = false;
if (type == 2) iv = r.ReadInt32(); else if (type == 3) bv = r.ReadBoolean(); else return;
Var v;
if (!vars.TryGetValue(key, out v) || v.Type != type) continue;
if (type == 2) ((FsmInt)v.Variable).Value = iv; else ((FsmBool)v.Variable).Value = bv;
Remember(v); // nicht zurückschicken
}
}
void Scan()
{
int before = vars.Count;
foreach (PlayMakerFSM fsm in SceneCache.Fsms(20f))
{
if (fsm == null || !scanned.Add(fsm)) continue;
if (Array.IndexOf(Roots, fsm.transform.root.name) < 0) continue;
string prefix = FsmSync.PathOf(fsm.transform) + "|" + fsm.FsmName + "|";
FsmVariables fv = fsm.FsmVariables;
foreach (FsmInt n in fv.IntVariables) Register(prefix, n, 2);
foreach (FsmBool b in fv.BoolVariables) Register(prefix, b, 3);
}
if (vars.Count != before) MscoLog.Info("[MSCO] Jobs/shop: " + vars.Count + " values synced");
}
void Register(string prefix, NamedVariable variable, byte type)
{
if (variable == null || string.IsNullOrEmpty(variable.Name)) return;
int key = Hash(prefix + variable.Name);
if (vars.ContainsKey(key)) return;
var v = new Var { Key = key, Variable = variable, Type = type };
Remember(v);
vars[key] = v;
}
static bool Changed(Var v)
{
return v.Type == 2 ? ((FsmInt)v.Variable).Value != v.LastI : ((FsmBool)v.Variable).Value != v.LastB;
}
static void Remember(Var v)
{
if (v.Type == 2) v.LastI = ((FsmInt)v.Variable).Value; else v.LastB = ((FsmBool)v.Variable).Value;
}
static int Hash(string s)
{
unchecked
{
uint h = 2166136261;
foreach (byte c in Encoding.UTF8.GetBytes(s)) { h ^= c; h *= 16777619; }
return (int)h;
}
}
}
}