//------------------------------------------------------------------------------------------------------- // disable objects that are in CTlist but go missing from CTworld list public void clearMissing(CTworld ctworld) { if (newSession) { return; } List <String> destroyList = new List <String>(); // make copy; avoid sync error foreach (KeyValuePair <string, GameObject> entry in CTlist) // cycle through objects in world { GameObject go = entry.Value; String goName = entry.Key; if ((go == null) || !go.activeSelf) // prune inactive objects { destroyList.Add(goName); } else { // don't deactivate locally owned Player objects (might be instantiated but not yet seen in ctworld) if (!activeWrite || !goName.StartsWith(Player)) { if (!ctworld.objects.ContainsKey(goName)) { CTclient ctc = go.GetComponent <CTclient>(); if (!(ctc != null && ctc.isRogue)) // let Rogue objects persist { destroyList.Add(goName); } } } } } // clear out destroylist foreach (String t in destroyList) { // Debug.Log("clearMissing: " + t); clearObject(t); // destroy while iterating wrecks list } }
//------------------------------------------------------------------------------------------------------- void printCTworld(CTworld ctworld) { String p = "CTworld:\n"; p += ("name: " + ctworld.player + "\n"); // redundant Key, name? // p += ("mode: " + ctworld.mode + "\n"); p += ("time: " + ctworld.time + "\n"); p += ("Objects:" + "\n"); if (ctworld.objects == null) { p += "<null>"; } else { foreach (CTobject ctobject in ctworld.objects.Values) { p += ("\tkey: " + ctobject.id + "\n"); // redundant Key, id? p += ("\tprefab: " + ctobject.model + "\n"); // object class // p += ("\tstate: " + ctobject.state + "\n"); p += ("\tpos: " + ctobject.pos + "\n"); } } UnityEngine.Debug.Log(p); }
public IEnumerator getGameState() { Boolean pendingSession = false; while (true) { if (gamePaused || (replayActive && (replayTime == oldTime))) // no dupes (e.g. paused) { yield return(null); // ease up until next Update() continue; } oldTime = replayTime; double thisTime = ServerTime(); double deltaTime = thisTime - lastTime; double pointTime = 1F / maxPointRate; if (replayActive) { waitTime = pointTime; } // Debug.Log("waitTime: " + waitTime + ", pointTime: " + pointTime + ", pollInterval: " + pollInterval + ", deltaWorldTime: " + deltaWorldTime+", deltaTime: "+deltaTime); // idle-loop until expected amount of time for next update if (deltaTime < waitTime || Session.Equals("")) { yield return(null); continue; } lastTime = thisTime; if (newSession) { pendingSession = true; } // form HTTP GET URL String urlparams = ""; // "?f=d" is no-op for wildcard request if (replayActive) { urlparams = "?t=" + replayTime; // replay at masterTime } else { urlparams = "?c=" + Math.Round(pollInterval * 1000F); // set cache interval } string url1 = Server + "/CT/" + Session + "/GamePlay/*/" + CTchannel + urlparams; // Debug.Log("url1: " + url1); // enclose in "using" to ensure www1 object properly disposed: using (UnityWebRequest www1 = UnityWebRequest.Get(url1)) { www1.SetRequestHeader("AUTHORIZATION", CTauthorization()); yield return(www1.SendWebRequest()); if (newSession && !pendingSession) { continue; } // proceed with parsing CTstates.txt if (!string.IsNullOrEmpty(www1.error) || www1.downloadHandler.text.Length < 10) { Debug.Log("HTTP Error: " + www1.error + ": " + url1); if (isReplayMode()) { clearWorlds(); // presume problem is empty world... } pendingSession = newSession = false; // bail (presume empty all-around) // Debug.Log("newSession OFF"); continue; } CTdebug(null); // clear error double stime = ServerTime(); if (stime > lastReadTime) // block/sec (moving avg) { // BPS = Math.Round((BPS + (1F / (stime - lastReadTime))) / 2F); if (BPS < 1F) { BPS = Math.Ceiling((BPS + (1F / (stime - lastReadTime))) / 2F); // don't round to 0 } else { BPS = Math.Round((BPS + (1F / (stime - lastReadTime))) / 2F); } } lastReadTime = stime; // parse to class structure... List <CTworld> ws = CTserdes.deserialize(this, www1.downloadHandler.text); CTworld CTW = mergeCTworlds(ws); if (CTW == null || CTW.objects == null) { continue; // notta } if (pendingSession) { pendingSession = newSession = false; // (re)enable Update getData } if (!replayActive) // got a new deltaWorldTime... { // if (deltaWorldTime > 0) waitTime = Math.Max(0F, pollInterval-deltaTime); // fast waitTime if active if (deltaWorldTime > 0) { waitTime = pollInterval; // fast waitTime if active } else { waitTime = Math.Min(PaceTime, waitTime * 1.1f); // grow waitTime if idle } } } } // end while(true) }
//------------------------------------------------------------------------------------------------------- // mergeCTworlds: consolidate CTworlds from each player into local world-state CTworld mergeCTworlds(List <CTworld> worlds) { if (worlds == null) { // CTdebug("Warning: null CTworlds!"); return(null); // nobody home } double masterTime = ServerTime(); double updateTime = 0F; // build CTWorld structure from consolidated objects as owned (prefix) by each world CTworld CTW = new CTworld(); CTW.player = masterWorldName; // nominal CTW header values (not actually used) CTW.time = masterTime; CTW.objects = new Dictionary <String, CTobject>(); List <CTworld> tworldList = new List <CTworld>(); // second pass, screen masterTime, consolidate masterWorld foreach (CTworld world in worlds) { if (world.time > updateTime && !world.player.Equals(Player)) { updateTime = world.time; // keep track of most-recent CTW time } double delta = Math.Abs(world.time - masterTime); // masterTime NG on Remote... ??? if ((SyncTime > 0) && (delta > SyncTime)) // reject stale times { if (!world.player.Equals(Player) /* || observerFlag */) { clearWorld(world.player); // inefficient? } continue; } // if (world.objects.Count > 0) // skip empty place-holder worlds { world.active = (delta < LinkDeadTime); tworldList.Add(world); // build list of active worlds } // Debug.Log("mergeWorlds player: " + world.player+", objects: "+world.objects.Count); foreach (KeyValuePair <String, CTobject> ctpair in world.objects) { CTobject ctobject = ctpair.Value; // if (!ctobject.id.StartsWith(world.player + "/")) ctobject.id = world.player + "/" + ctobject.id; // auto-prepend world name to object // accumulate objects owned by each world try { CTW.objects.Add(ctobject.id, ctobject); } catch (Exception e) { Debug.Log("CTW exception on object: " + ctobject.id + ", e: " + e); } // set object state with CTclient GameObject mygo; Boolean isOnList = CTlist.TryGetValue(ctobject.id, out mygo); if (isOnList && mygo != null) // object exists { if ((isReplayMode() || !world.player.Equals(Player))) // check for change of prefab { string pf = mygo.transform.GetComponent <CTclient>().prefab; if (!pf.Equals(ctobject.model)) // recheck showMenu for async newPlayer { // Debug.Log("change prefab: " + ctobject.model + " --> " + pf); newGameObject(ctobject); } } setState(ctobject.id, ctobject, mygo); // use pre-fetched mygo if possible } else // instantiate new players and objects { if ((newSession || replayActive || !world.player.Equals(Player))) // mjm 12/3/18 { newGameObject(ctobject); } setState(ctobject.id, ctobject); } } } // scan for missing objects clearMissing(CTW); deltaWorldTime = updateTime - latestWorldTime; latestWorldTime = updateTime; // for replay reference WorldList = tworldList; // update set of worlds // Debug.Log("new Worldlist!"); foreach(CTworld ctw in WorldList) Debug.Log("ctw: " + ctw.player+", time: "+ctw.time+", nobjects: "+ctw.objects.Count); return(CTW); // consolidated CTworld }
/// <summary> /// Deserialize the given JSON string into a List of CTworld objects. /// The given string may contain one or a concatenation of several "world" objects. /// /// JSON example: /// {"mode":"Live","time":1.536844452785E9,"name":"Blue","objects":[{"id":"Blue","prefab":"Ball","state":true,"pos":[-8.380356788635254,0.25,3.8628578186035156],"rot":[0.0,0.0,0.0],"custom":""}]} /// /// </summary> /// <param name="strI">The JSON serialized world objects.</param> /// <returns>A List of CTworlds, parsed from the given string.</returns> private static List <CTworld> deserialize_json(CTunity ctunityI, string strI) { if ((strI == null) || (strI.Length < 10)) { return(null); } List <CTworld> worlds = new List <CTworld>(); // Break up the given string into different "world" objects // NOTE: This assumes that "player" is the first field in the JSON string List <int> indexes = AllIndexesOf(strI, @"{""player"":"""); if (indexes.Count == 0) { return(null); } for (int i = 0; i < indexes.Count; ++i) { int startIdx = indexes[i]; int endIdx = strI.Length - 1; if (i < (indexes.Count - 1)) { endIdx = indexes[i + 1]; } string nextWorldStr = strI.Substring(startIdx, endIdx - startIdx); CTworldJson dataFromJson = null; try { dataFromJson = JsonUtility.FromJson <CTworldJson>(nextWorldStr); } catch (Exception e) { UnityEngine.Debug.Log("Exception deserializing JSON: " + e.Message); continue; } if (dataFromJson == null || dataFromJson.objects == null) { continue; } // skip active local player ( save the effort here and in MergeWorlds() ) // ?? seems to have issues PB <--> RT ?? // if(ctunityI.activePlayer(dataFromJson.player)) continue; // Create CTworld object from CTworldJson (these classes are very similar but there are differences, see definitions above) CTworld jCTW = new CTworld(); jCTW.player = dataFromJson.player; jCTW.time = dataFromJson.time; // jCTW.mode = dataFromJson.mode; jCTW.objects = new Dictionary <String, CTobject>(); foreach (CTobjectJson ctobject in dataFromJson.objects) { CTobject cto = new CTobject(); cto.id = ctobject.id; // legacy format conversion: // if (cto.id.Equals(jCTW.player)) cto.id = cto.id + "/" + cto.id; // convert old top-level player // else if (cto.id.StartsWith(jCTW.player + ".")) cto.id = cto.id.Remove(0, jCTW.player.Length + 1); // Debug.Log("deserialize id: " + cto.id); cto.model = ctobject.model; // cto.state = ctobject.state; // cto.link = ctobject.link; cto.pos = new Vector3((float)ctobject.pos[0], (float)ctobject.pos[1], (float)ctobject.pos[2]); cto.rot = Quaternion.Euler((float)ctobject.rot[0], (float)ctobject.rot[1], (float)ctobject.rot[2]); cto.scale = new Vector3((float)ctobject.scale[0], (float)ctobject.scale[1], (float)ctobject.scale[2]); cto.color = new Color((float)ctobject.color[0], (float)ctobject.color[1], (float)ctobject.color[2], (float)ctobject.color[3]); cto.custom = ctobject.custom; jCTW.objects.Add(cto.id, cto); } worlds.Add(jCTW); } if (worlds.Count == 0) { return(null); } return(worlds); }
/// <summary> /// Deserialize the given txt/csv string into a List of CTworld objects. /// /// CSV example: /// #Live:1536843969.4578:Red /// Red;Ball;1;(2.5764, 0.2600, 8.5905);(0.0277, 60.7938, -0.0043) /// Red.Ground;Ground;1;(0.0000, 0.0000, 20.0000);(0.0000, 0.0000, 0.0000) /// Red.Pickup0;Pickup;1;(0.2000, 0.4000, 23.0000);(45.0800, 20.8186, 21.9459) /// Red.Pickup1;Pickup;1;(8.1000, 0.4000, 27.4000);(45.0800, 20.8186, 21.9459) /// Red.Pickup2;Pickup;1;(-1.3000, 0.4000, 13.3000);(45.0800, 20.8186, 21.9459) /// Red.Pickup3;Pickup;1;(2.0000, 0.4000, 23.6000);(45.0800, 20.8186, 21.9459) /// Red.Pickup4;Pickup;1;(0.9000, 0.4000, 22.7000);(45.0800, 20.8186, 21.9459) /// /// </summary> /// <param name="strI">The csv serialized world objects.</param> /// <returns>A List of CTworlds, parsed from the given string.</returns> private static List <CTworld> deserialize_csv(string strI) { List <CTworld> worlds = new List <CTworld>(); String[] sworlds = strI.Split('#'); // parse world-by-world // consolidate objects for each world foreach (String world in sworlds) { // Debug.Log("world: " + world); if (world.Length < 2) { continue; } String[] lines = world.Split('\n'); String[] header = lines[0].Split(':'); if (header.Length != 3) { continue; } CTworld CTW = new CTworld(); CTW.objects = new Dictionary <String, CTobject>(); // CTW.mode = header[0]; CTW.time = Double.Parse(header[1]); CTW.player = header[2]; foreach (String line in lines) { // sanity checks: if (line.Length < 2 || line.StartsWith("<")) { continue; // skip empty & html lines } String[] parts = line.Split(';'); if (parts.Length < 3) { continue; } CTobject ctobject = new CTobject(); ctobject.id = parts[0]; ctobject.model = parts[1]; // ctobject.state = !parts[2].Equals("0"); // parse ctobject.pos string pstate = parts[3].Substring(1, parts[3].Length - 2); // drop parens string[] pvec = pstate.Split(','); ctobject.pos = new Vector3(float.Parse(pvec[0]), float.Parse(pvec[1]), float.Parse(pvec[2])); // parse ctobject.rot pstate = parts[4].Substring(1, parts[4].Length - 2); // drop parens pvec = pstate.Split(','); ctobject.rot = Quaternion.Euler(float.Parse(pvec[0]), float.Parse(pvec[1]), float.Parse(pvec[2])); // custom field (used for indirect URL): // if (parts.Length > 5) ctobject.link = parts[5]; CTW.objects.Add(ctobject.id, ctobject); } worlds.Add(CTW); } return(worlds); }