Last active
October 21, 2020 05:08
-
-
Save modality/7fbdd466c98c2047914ff330f0fe9943 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Linq; | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public interface ITopic { | |
bool IsClosed(); | |
void Update(); | |
void Close(); | |
string ToString(); | |
} | |
public class Topic<T> : ITopic where T : new() | |
{ | |
T value = new T(); | |
string name; | |
public event Action<T> channels; | |
public HashSet<string> aliases; | |
public bool everPublished; | |
public bool dirty; | |
public bool closing; | |
public bool closed; | |
public Topic(string name) | |
{ | |
this.name = name; | |
channels = delegate { }; | |
aliases = new HashSet<string>(); | |
everPublished = false; | |
dirty = false; | |
closing = false; | |
closed = false; | |
} | |
public void UpdateValue(T value) { | |
if (closed) { | |
return; | |
} | |
if (!everPublished || !value.Equals(this.value)) { | |
this.value = value; | |
dirty = true; | |
everPublished = true; | |
} | |
} | |
public T Value() { | |
return value; | |
} | |
public void Update() { | |
if (closed) { | |
return; | |
} | |
Publish(); | |
if (closing) { | |
closed = true; | |
} | |
} | |
void Publish() { | |
if (dirty) { | |
try { | |
channels(value); | |
foreach (string alias in aliases) { | |
Publisher.Instance.Publish<T>(alias, value); | |
} | |
} catch { | |
Debug.LogError("Error publishing "+this.name); | |
throw; | |
} | |
dirty = false; | |
} | |
} | |
public void Close() { | |
closing = true; | |
} | |
public bool IsClosed() { | |
return closed; | |
} | |
public void Subscribe(Action<T> listener) { | |
if (everPublished) { | |
listener(value); | |
} | |
channels += listener; | |
} | |
public void Unsubscribe(Action<T> listener) { | |
channels -= listener; | |
} | |
public Delegate[] Subscribers() | |
{ | |
return channels.GetInvocationList(); | |
} | |
public void AddAlias(string alias) | |
{ | |
aliases.Add(alias); | |
if (everPublished) { | |
Publisher.Instance.Publish<T>(alias, value); | |
} | |
} | |
public void RemoveAlias(string alias) | |
{ | |
aliases.Remove(alias); | |
} | |
public string ToString() | |
{ | |
List<string> output = new List<string>(); | |
output.Add($"[{name}]"); | |
output.Add($"aliases: {System.String.Join(", ", aliases)}"); | |
output.Add($"ever pub'd: {everPublished}"); | |
output.Add($"closed: {closed}"); | |
return System.String.Join("\n", output); | |
} | |
} | |
public class Publisher : MonoBehaviour { | |
public static Publisher Instance { get; set; } | |
bool dirty; | |
int maxFrames; | |
float maxTime; | |
float minTime; | |
float[] rolling; | |
int rollingIndex; | |
Dictionary<string, ITopic> topics = new Dictionary<string, ITopic>(); | |
Dictionary<string, string> aliases = new Dictionary<string, string>(); | |
void Awake() | |
{ | |
rolling = new float[1000]; | |
rollingIndex = 0; | |
if (Instance != null && Instance != this) { | |
Destroy(this.gameObject); | |
} else { | |
Instance = this; | |
} | |
} | |
void Update() | |
{ | |
if (dirty) { | |
Publisher.Instance.Publish<List<string>>("Publisher.Topics", topics.Keys.ToList()); | |
dirty = false; | |
} | |
} | |
void OnApplicationQuit() | |
{ | |
double min = System.Math.Round(minTime*1000, 2); | |
double max = System.Math.Round(maxTime*1000, 2); | |
double avg = System.Math.Round(rolling.Average()*1000, 2); | |
Debug.Log($"PUBLISHER STATS: min - {min}ms avg - {avg}ms max - {max}ms // max frames to process {maxFrames}"); | |
} | |
public void Init() | |
{ | |
StartCoroutine(PublishDebounce()); | |
} | |
public IEnumerator PublishDebounce() { | |
float maximumTimePerFrame = 1.0f / 50; // 50 fps | |
float timeStamp; | |
int frames; | |
bool yieldOvertime = true; | |
maxTime = 0f; | |
minTime = 1f; | |
maxFrames = 0; | |
while (true) { | |
timeStamp = Time.realtimeSinceStartup; | |
frames = 0; | |
// make a copy first, because calling Update on a topic may add new topics/workers | |
ITopic[] currentTopics = topics.Values.ToArray(); | |
foreach(ITopic topic in currentTopics) { | |
topic.Update(); | |
if (yieldOvertime && Time.realtimeSinceStartup > timeStamp + maximumTimePerFrame) | |
{ | |
frames += 1; | |
yield return null; // wait for next frame of gameplay | |
timeStamp = Time.realtimeSinceStartup; | |
} | |
} | |
// cull closed topics | |
int count = topics.Count; | |
topics = topics.Where(t => !t.Value.IsClosed()).ToDictionary(t => t.Key, t => t.Value); | |
if (topics.Count < count) { | |
dirty = true; | |
} | |
// profilin' | |
float duration = Time.realtimeSinceStartup - timeStamp; | |
ProfileOneIteration(duration, frames); | |
yield return null; | |
} | |
} | |
public void ProfileOneIteration(float duration, int frameCount) | |
{ | |
if (duration > maxTime) { | |
maxTime = duration; | |
} | |
if (duration < minTime) { | |
minTime = duration; | |
} | |
if (frameCount > maxFrames) { | |
maxFrames = frameCount; | |
} | |
rolling[rollingIndex] = duration; | |
rollingIndex += 1; | |
rollingIndex = rollingIndex % rolling.Length; | |
} | |
public void Subscribe<T>(string key, Action<T> action) where T : new() | |
{ | |
Topic<T> topic = GetTopic<T>(key); | |
try { | |
topic.Subscribe(action); | |
} catch { | |
Debug.LogError($"Error subscribing to topic {key} (usually a type problem)"); | |
throw; | |
} | |
} | |
public void Unsubscribe<T>(string key, Action<T> action) where T : new() | |
{ | |
if (topics.ContainsKey(key)) { | |
Topic<T> topic = GetTopic<T>(key); | |
try { | |
topic.Unsubscribe(action); | |
} catch { | |
Debug.LogError($"Error unsubscribing from topic {key} (usually a type problem)"); | |
throw; | |
} | |
} | |
} | |
public Topic<T> GetTopic<T>(string key) where T : new() | |
{ | |
if (!topics.ContainsKey(key)) { | |
topics[key] = new Topic<T>(key); | |
dirty = true; | |
} | |
return topics[key] as Topic<T>; | |
} | |
public void CloseTopic(string key) | |
{ | |
if (!topics.ContainsKey(key)) { | |
Debug.LogError($"can't close non-existent topic {key}"); | |
return; | |
} | |
topics[key].Close(); | |
} | |
public T GetTopicValue<T>(string key) where T : new() | |
{ | |
return GetTopic<T>(key).Value(); | |
} | |
public void Publish<T>(string key, T value) where T : new() | |
{ | |
Topic<T> topic = GetTopic<T>(key); | |
if (value != null) { | |
topic.UpdateValue(value); | |
} | |
} | |
public void AddAlias<T>(string alias, string original) where T : new() | |
{ | |
if (aliases.ContainsKey(alias)) { | |
RemoveAlias<T>(alias); | |
} | |
aliases[alias] = original; | |
GetTopic<T>(original).AddAlias(alias); | |
} | |
public void RemoveAlias<T>(string alias) where T : new() | |
{ | |
if (aliases.ContainsKey(alias)) { | |
string original = aliases[alias]; | |
aliases.Remove(alias); | |
GetTopic<T>(original).RemoveAlias(alias); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class Character : MonoBehaviour | |
{ | |
int health; | |
string healthTopic; | |
string characterName; | |
void Awake() | |
{ | |
health = 3; | |
healthTopic = $"Character.{GetInstanceID()}.Health"; | |
} | |
void Start() | |
{ | |
if (characterName == "Main Character") { | |
Publisher.AddAlias<object>("MainCharacter.Health", healthTopic); | |
} | |
} | |
void OnDestroy() | |
{ | |
Publisher.CloseTopic(healthTopic); | |
} | |
void TakeDamage() | |
{ | |
health -= 1; | |
Publisher.Instance.Publish<object>(healthTopic, health); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using TMPro; | |
public abstract class SimpleView : MonoBehaviour | |
{ | |
// set this topic in your inspector, for example "MainCharacter.Health" | |
public string topic; | |
// this is just a UI element | |
public TextMeshProUGUI textObject; | |
public virtual void OnEnable() | |
{ | |
if (topic != "") { | |
Publisher.Instance.Subscribe<object>(topic, RenderTemplate); | |
} | |
} | |
public virtual void OnDisable() | |
{ | |
if (topic != "") { | |
Publisher.Instance.Unsubscribe<object>(topic, RenderTemplate); | |
} | |
} | |
public virtual void SetBinding(string topic) | |
{ | |
if (this.topic != "") { | |
Publisher.Instance.Unsubscribe<object>(this.topic, RenderTemplate); | |
} | |
this.topic = topic; | |
if (this.topic != "") { | |
Publisher.Instance.Subscribe<object>(this.topic, RenderTemplate); | |
} | |
} | |
private void RenderTemplate(object incoming) | |
{ | |
textObject.text = (string) incoming; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment