Is there a way to access all the different types of object inherited from the same class using the same reference in an object without hard-coding it?
I'm developing on unity and I want to add a module in my game that could watch any particular float in a GameObject to then change another float in another GameObject once it reaches a certain value.
As an example: A "Trigger" Object/Module which set the value of Hunger=1 in a brain Object when the value of Fullness<0.5 is reached in a stomach Object.
As I'll have a big number of possible combinations I don't want to hardcode it by creating a daughter of the Trigger Class for each of them.
My initial idea was to use pointers that would be directed to the good floats to watch/change upon initialization. But apparently, we can't use unsafe code inside iterators (IEnumerator) so I'm not sure how to poll the value of Fullness.
To give an example of what I would like :
public Class Trigger{
private float* ToPoll;
private float* ToChange;
public void SetTrigger(float* poll, float* change){
ToPoll = poll;
ToChange = change;
// the loop would be a IEnumerator, not a litteral loop
while(*ToPoll < 0.5f){
sleep(0.1)
}
*ToChange = 1f
}
}
void Main(){
Trigger trigger1, trigger2;
trigger1.SetTrigger(&Stomach.fullness, &Brain.hunger)
trigger2.SetTrigger(&Sun.activityLevel, &Earth.radiationLevel)
// ^ Literally any float from any object
}
Do you have any ideas how to or better ways to implement it?
Expanding on the answer from #kara, the following code implements independent Stomach and Brain objects, and uses Being to wire them up.
What Being knows about Stomach:
it has a NeedsFoodEvent
What Being knows about Brain
there is a OnRaiseIsHungryEvent (i.e. a "hungry" signal -- who cares where it came from)
it has a IsHungryEvent
Keep in mind that in a real implementation there would likely be other objects listening for those events. e.g. maybe you have an emotion system that would switch to "hangry" and a goal-based AI that would switch to food-seeking mode. Neither system would need to be aware of the other, but both could respond to signals from the Brain. In this trivial implementation the Being responds to the Stomach signal and both notifies and responds to the Brain.
The important take-away from this is not the specific method of raising and responding to events (in this case the default .Net mechanism) but the fact that neither object knows anything about the internals of the other (see the different implementations of HumanStomach and ZombieStomach) and instead the connection is wired up at a more appropriate level (Being in this case). Also note the reliance on interfaces, which allows us to do things like create hybrid beings (i.e. pairing a ZombieBrain with a HumanStomach).
Code was written/tested with .Net Core CLI as a console app, but it should be compatible with most any version of .Net > 3.5.
using System;
using System.Linq;
using System.Threading;
namespace so_example
{
public class Program
{
static void Main(string[] args)
{
var person1 = new Being("Human 1", new HumanBrain(), new HumanStomach());
var zombie1 = new Being("Zombie 1", new ZombieBrain(), new ZombieStomach());
var hybrid1 = new Being("Hybrid 1", new ZombieBrain(), new HumanStomach());
var hybrid2 = new Being("Hybrid 2", new HumanBrain(), new ZombieStomach());
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
public class HungryEventArgs : EventArgs
{
public string Message { get; set; }
}
public interface IStomach
{
event EventHandler<HungryEventArgs> NeedsFoodEvent;
}
public class Stomach : IStomach
{
public event EventHandler<HungryEventArgs> NeedsFoodEvent;
protected virtual void OnRaiseNeedsFoodEvent(HungryEventArgs e)
{
EventHandler<HungryEventArgs> handler = NeedsFoodEvent;
if (handler != null)
{
handler(this, e);
}
}
}
public class HumanStomach : Stomach
{
private Timer _hungerTimer;
public HumanStomach()
{
_hungerTimer = new Timer(o =>
{
// only trigger if breakfast, lunch or dinner (24h notation)
if (new [] { 8, 13, 19 }.Any(t => t == DateTime.Now.Hour))
{
OnRaiseNeedsFoodEvent(new HungryEventArgs { Message = "I'm empty!" });
}
else
{
Console.WriteLine("It's not mealtime");
}
}, null, 1000, 1000);
}
}
public class ZombieStomach : Stomach
{
private Timer _hungerTimer;
public ZombieStomach()
{
_hungerTimer = new Timer(o =>
{
OnRaiseNeedsFoodEvent(new HungryEventArgs { Message = "Need brains in stomach!" });
}, null, 1000, 1000);
}
}
public interface IBrain
{
event EventHandler<HungryEventArgs> IsHungryEvent;
void OnRaiseIsHungryEvent();
}
public class Brain : IBrain
{
public event EventHandler<HungryEventArgs> IsHungryEvent;
protected string _hungryMessage;
public void OnRaiseIsHungryEvent()
{
EventHandler<HungryEventArgs> handler = IsHungryEvent;
if (handler != null)
{
var e = new HungryEventArgs
{
Message = _hungryMessage
};
handler(this, e);
}
}
}
public class HumanBrain : Brain
{
public HumanBrain()
{
_hungryMessage = "Need food!";
}
}
public class ZombieBrain : Brain
{
public ZombieBrain()
{
_hungryMessage = "Braaaaaains!";
}
}
public class Being
{
protected readonly IBrain _brain;
protected readonly IStomach _stomach;
private readonly string _name;
public Being(string name, IBrain brain, IStomach stomach)
{
_stomach = stomach;
_brain = brain;
_name = name;
_stomach.NeedsFoodEvent += (s, e) =>
{
Console.WriteLine($"{_name}: {e.Message}");
_brain.OnRaiseIsHungryEvent();
};
_brain.IsHungryEvent += (s, e) =>
{
Console.WriteLine($"{_name}: {e.Message}");
};
}
}
}
Some Notes
To provide some output, I faked things in the 2 IStomach implementations. The HumanStomach creates a timer callback in the constructor which fires every 1 second and checks if the current hour is a meal hour. If it is, it raises the NeedsFoodEvent. The ZombieStomach also uses a callback every 1 second, but it just fires the NeedsFoodEvent every time. In a real Unity implementation you'd likely trigger the even based on some event from Unity -- an action the player took, after some preset amount of time, etc.
I'm not quite sure, what you want to do, but it sounds like you want to add triggers to you objects. In my understanding a trigger should be a delegate in this case.
Here an example how to define a delegate-type and add a list of triggers to your Brain-class.
Every brain can now have different triggers. I setup two derived brains to show you how to work with it:
public class TestBrain
{
private static int NextId = 1;
public TestBrain(List<MyTrigger> triggers)
{
this.Triggers = triggers;
this.Id = NextId++;
}
public int Id { get; private set; }
public int Hunger { get; set; }
public int StomachFullness { get; set; }
public List<MyTrigger> Triggers { get; private set; }
public void FireTriggers()
{
foreach (MyTrigger t in this.Triggers)
{
t.Invoke(this);
this.StomachFullness = 100;
}
}
public delegate void MyTrigger(TestBrain b);
}
public class HumanBrain : TestBrain
{
static readonly List<MyTrigger> defaultHumanTriggers = new List<MyTrigger>()
{
b => { if (b.StomachFullness < 50) { b.Hunger = 1; Console.WriteLine("{0} is hungry..", b.Id); } }
};
public HumanBrain() : base(defaultHumanTriggers)
{
}
}
public class RobotBrain : TestBrain
{
static readonly List<MyTrigger> defaultRobotTriggers = new List<MyTrigger>()
{
b => { if (b.StomachFullness < 50) { Console.WriteLine("{0} ignores hunger only want's some oil..", b.Id); } }
};
public RobotBrain() : base(defaultRobotTriggers)
{
}
}
static void Main()
{
// Create some test-data
List<TestBrain> brains = new List<TestBrain>()
{
new HumanBrain(),
new HumanBrain(),
new RobotBrain(),
new HumanBrain(),
};
Console.WriteLine(" - - - Output our Testdata - - -");
foreach (TestBrain b in brains)
{
Console.WriteLine("Status Brain {0} - Stomachfulness: {1} Hunger: {2}", b.Id, b.StomachFullness, b.Hunger);
}
Console.WriteLine(" - - - Empty stomachs - - -");
foreach (TestBrain b in brains)
{
b.StomachFullness = 0;
}
Console.WriteLine(" - - - Fire triggers - - -");
foreach (TestBrain b in brains)
{
b.FireTriggers();
}
Console.WriteLine(" - - - Output our Testdata - - -");
foreach (TestBrain b in brains)
{
Console.WriteLine("Status Brain {0} - Stomachfulness: {1} Hunger: {2}", b.Id, b.StomachFullness, b.Hunger);
}
}
Related
Background
I'm in a need for a queued message broker dispatching messages in a distributed (over consecutive frames) manner. In the example shown below it will process no more than 10 subscribers, and then wait for the next frame before processing further.
(For the sake of clarification for those not familiar with Unity3D, Process() method is run using Unity's built-in StartCoroutine() method and - in this case - will last for the lifetime of the game - waiting or processing from the queue.)
So i have such a relatively simple class:
public class MessageBus : IMessageBus
{
private const int LIMIT = 10;
private readonly WaitForSeconds Wait;
private Queue<IMessage> Messages;
private Dictionary<Type, List<Action<IMessage>>> Subscribers;
public MessageBus()
{
Wait = new WaitForSeconds(2f);
Messages = new Queue<IMessage>();
Subscribers = new Dictionary<Type, List<Action<IMessage>>>();
}
public void Submit(IMessage message)
{
Messages.Enqueue(message);
}
public IEnumerator Process()
{
var processed = 0;
while (true)
{
if (Messages.Count == 0)
{
yield return Wait;
}
else
{
while(Messages.Count > 0)
{
var message = Messages.Dequeue();
foreach (var subscriber in Subscribers[message.GetType()])
{
if (processed >= LIMIT)
{
processed = 0;
yield return null;
}
processed++;
subscriber?.Invoke(message);
}
}
processed = 0;
}
}
}
public void Subscribe<T>(Action<IMessage> handler) where T : IMessage
{
if (!Subscribers.ContainsKey(typeof(T)))
{
Subscribers[typeof(T)] = new List<Action<IMessage>>();
}
Subscribers[typeof(T)].Add(handler);
}
public void Unsubscribe<T>(Action<IMessage> handler) where T : IMessage
{
if (!Subscribers.ContainsKey(typeof(T)))
{
return;
}
Subscribers[typeof(T)].Remove(handler);
}
}
And it works and behaves just as expected, but there is one problem.
The problem
I would like to use it (from the subscriber's point of view) like this:
public void Run()
{
MessageBus.Subscribe<TestEvent>(OnTestEvent);
}
public void OnTestEvent(TestEvent message)
{
message.SomeTestEventMethod();
}
But this obviously fails because Action<IMessage> cannot be converted to Action<TestEvent>.
The only way i can use it is like this:
public void Run()
{
MessageBus.Subscribe<TestEvent>(OnTestEvent);
}
public void OnTestEvent(IMessage message)
{
((TestEvent)message).SomeTestEventMethod();
}
But this feels unelegant and very wasteful as every subscriber needs to do the casting on it's own.
What i have tried
I was experimenting with "casting" actions like that:
public void Subscribe<T>(Action<T> handler) where T : IMessage
{
if (!Subscribers.ContainsKey(typeof(T)))
{
Subscribers[typeof(T)] = new List<Action<IMessage>>();
}
Subscribers[typeof(T)].Add((IMessage a) => handler((T)a));
}
And this works for the subscribe part, but obviously not for the unsubscribe. I could cache somewhere newly created handler-wrapper-lambdas for use when unsubscribing, but i don't think this is the real solution, to be honest.
The question
How can i make this to work as i would like to? Preferably with some C# "magic" if possible, but i'm aware it may require a completely different approach.
Also because this will be used in a game, and be run for it's lifetime i would like a garbage-free solution if possible.
So the problem is that you are trying to store lists of a different type as values in the subscriber dictionary.
One way to get around this might be to store a List<Delegate> and then to use Delegate.DynamicInvoke.
Here's some test code that summarizes the main points:
Dictionary<Type, List<Delegate>> Subscribers = new Dictionary<Type, List<Delegate>>();
void Main()
{
Subscribe<Evt>(ev => Console.WriteLine($"hello {ev.Message}"));
IMessage m = new Evt("spender");
foreach (var subscriber in Subscribers[m.GetType()])
{
subscriber?.DynamicInvoke(m);
}
}
public void Subscribe<T>(Action<T> handler) where T : IMessage
{
if (!Subscribers.ContainsKey(typeof(T)))
{
Subscribers[typeof(T)] = new List<Delegate>();
}
Subscribers[typeof(T)].Add(handler);
}
public interface IMessage{}
public class Evt : IMessage
{
public Evt(string message)
{
this.Message = message;
}
public string Message { get; }
}
I am working on a cardgame conform the MVVM pattern. My model contains the players, hands and cards as well as the game with its rules.
There are 2 classes not playing nice here: the "card" class that has a "submitted" event: when a player clicks on an image of the card, among other things the submitted event fires. This triggers the UI to move the card from a hand to the center of the window.
Next I have a class "trick", that all players add a card to. When the trick is full, it fires the TrickFull event: this triggers the UI to show the cards in the trick and then clear the table.
During gameplay the TrickFull event fires nanoseconds after the last card was submitted. This means the table is cleared before the 4th card can be shown. I would like to be able to force the UI to process the cardsubmitted event before the Trickfull event.
I have tried to accomplish this by Thread.Sleep (which does not work), I have also tried to move the TrickFull event to the gameclass (meaning it gets triggered much later). This works, but it does seem very out of place. I have looked into locking the events (but that does not seem to be the way to go), directly taking to control of the Dispatcher, changing the priority, or maybe calling the events asynchonously and blocking the stuff somehow in the EndInvoke.
I would like to know what the best solution for this would be. My research suggests that maybe Events would not be the best pattern for this behaviour, but I am stumped. Can you bright people please advise me on how to fix this (probably architectural) flaw?
Code below, beware: Dutch classnames and stuff in there
Card (=Kaart)
public class Kaart : IComparable<Kaart>
{
public readonly Kleur Kleur;
public readonly Waarde Waarde;
public Kaart(Kleur kleur, Waarde waarde)
{
Kleur = kleur;
Waarde = waarde;
}
public event KaartGespeeld Opgegooid;
public delegate void KaartGespeeld(Kaart kaart);
public void Opgooien()
{
Opgegooid?.Invoke(this);
}
public int CompareTo(Kaart other)
{
var comparer = new KlaverjasComparer(null, null);
return comparer.Compare(this, other);
}
public Speler Speler { get; set; }
}
Trick (=Slag)
public class Slag
{
private readonly List<Kaart> _kaarten;
[Browsable(false)]
public IReadOnlyList<Kaart> Kaarten => _kaarten;
public Slag(Kleur troef)
{
_kaarten = new List<Kaart>(4);
Troef = troef;
}
public Speler Winnaar { get; private set; }
public int Punten => PuntenTeller.Punten(this);
public int Roem => PuntenTeller.Roem(this);
[Browsable(false)]
public Kleur Troef { get; }
public Kleur GevraagdeKleur { get; set; }
[Browsable(false)]
public bool Vol =>_kaarten.Count == 4;
public void Add(Kaart kaart)
{
if (!Vol)
{
if (_kaarten.Count == 0)
{
GevraagdeKleur = kaart.Kleur;
}
_kaarten.Add(kaart);
}
else
{
throw new Exception("Te veel kaarten in een slag");
}
if (!Vol) return;
Winnaar = bepaalHoogsteKaart(this).Speler;
VolleSlag?.Invoke(this);
}
public event SlagIsVol VolleSlag;
public delegate void SlagIsVol(Slag slag);
}
ViewModel:
public TafelViewModel(Boompje boompje)
{
Speler1 = boompje.Deelnemers[0];
Speler2 = boompje.Deelnemers[1];
Speler3 = boompje.Deelnemers[2];
Speler4 = boompje.Deelnemers[3];
Troef = boompje.Potje.Troef;
//boompje.SlagIsVol += Boompje_SlagIsVol;
// ToDo: als ik naar dit event kijk gaat het mis
boompje.Potje.Slag.VolleSlag += Boompje_SlagIsVol;
boompje.Potje.TroefGedraaid += delegate { Troef = boompje.Potje.Troef; };
foreach (Speler _deelnemer in boompje.Deelnemers)
{
foreach (Kaart _kaart in _deelnemer.Hand)
{
_kaart.Opgegooid += moveKaart;
}
_deelnemer.DoeIkHet += DeelnemerOnDoeIkHet;
}
_spelerKaart = new Dictionary<Speler, string>
{
{Speler1, "Kaart1"},
{Speler2, "Kaart2"},
{Speler3, "Kaart3"},
{Speler4, "Kaart4"}
};
_spelerRichting = Dictionary.SpelersRichting(boompje.Deelnemers);
WinnaarVisible = Visibility.Hidden;
}
private void Boompje_SlagIsVol(Slag slag)
{
WinnaarVisible = Visibility.Visible;
Richting = _spelerRichting[slag.Winnaar];
Application.DoEvents();
Thread.Sleep(2000);
Kaart1 = null;
Kaart2 = null;
Kaart3 = null;
Kaart4 = null;
WinnaarVisible = Visibility.Hidden;
}
private void moveKaart(Kaart kaart)
{
PropertyInfo prop = GetType().GetProperty(_spelerKaart[kaart.Speler]);
prop?.SetValue(this, kaart);
}
public void OpKaartGeklikt(Kaart kaart)
{
if (kaart.Speler != Speler3)
{
return;
}
Speler3.SpeelKaart(kaart);
}
}
}
Set ManualResetEvent in your ViewModel
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Pass this object into Kaart of yours
ManualResetEvent _manualResetEvent;
public Kaart(Kleur kleur, Waarde waarde, ManualResetEvent manualResetEvent)
{
Kleur = kleur;
Waarde = waarde;
_manualResetEvent = manualResetEvent;
}
This is the method that's being invoked when card is added I assume
public void Opgooien()
{
Opgegooid?.Invoke(this);
_manualResetEvent.Set();
}
And the main part (you also need to pass ManualResetEvent to the Slag object.
public void Add(Kaart kaart)
{
if (!Vol)
{
if (_kaarten.Count == 0)
{
GevraagdeKleur = kaart.Kleur;
}
_kaarten.Add(kaart);
}
else
{
throw new Exception("Te veel kaarten in een slag");
}
if (!Vol) return;
Winnaar = bepaalHoogsteKaart(this).Speler;
var result = _manualResetEvent.WaitOne(TimeSpan.FromSeconds(5));
if(!result)
{
/* Did not receive signal in 5 seconds */
}
VolleSlag?.Invoke(this);
_manualResetEvent.Reset();
}
Just the basic concept, it might not work right away due to your code language and lack of some part of it in the example, but you should catch the idea
I'm making QuestSystem in unity.
what i want to do is assigning to my questData an event so it can know when the quest objective has been completed.
Lets say there is a class named A and action called a.
and i want Class B, Action b want to have reference to A.a
So if i do
b = A.a;,
b+= someAction;, it actually does a+=someAction;
but when if i do that. It will just simply b+=someAction and A.a will remain null
what should i do to perform what i want?
here are some tags. (i don't know what the answer would be. so..)
# event
# subscribing event
# assigning event
# referencing event
# action
# delegate
====== Edited =======
here is my code.
QuestData.cs
public class QuestData
{
public string questName;
public string qusetID;
public string questDescription;
public SceneType questSceneType;
private string isActivePrefsKey { get { return $"QuestKey{qusetID}"; } }
public bool isActive {
get {
return Convert.ToBoolean (PlayerPrefs.GetInt (isActivePrefsKey));
}
set { PlayerPrefs.SetInt (isActivePrefsKey, Convert.ToInt16 (value)); }
}
public QuestObjective questObjective;
public QuestReward questReward;
public void Activate ()
{
if (AppController.CurrentScene == questSceneType) {
questObjective.ActivateObjective ();
}
}
}
QuestObjective.cs
public class QuestObjective
{
// TODO rename all
public int goalObjectiveCount;
public int currentObjectiveCount;
public Action questAction;
public void OnConditionMatch ()
{
Debug.Log ("OnConditionMatch");
currentObjectiveCount += 1;
}
public void ActivateObjective ()
{
questAction += OnConditionMatch;
}
}
QuestManager.cs
public class QuestManager : MonoBehaviour
{
List<QuestData> questDatas;
void Awake ()
{
PrepareQuestDatas ();
ActivateActiveQuests ();
}
void ActivateActiveQuests ()
{
var activeQuests = GetActiveQuests ();
foreach (var activeQuest in activeQuests) {
activeQuest.Activate ();
}
}
List<QuestData> GetActiveQuests ()
{
// for debuging
return questDatas;
// real code
return questDatas.Where (q => q.isActive == true).ToList ();
}
public void PrepareQuestDatas ()
{
questDatas = new List<QuestData> {
new QuestData {
questName = "Foot Print",
questDescription = "win the game for first time",
questSceneType = SceneType.Main,
questObjective = new QuestObjective {
goalObjectiveCount = 1,
questAction = GamePlayController.instance.endGameCon.onWinGame
},
questReward = new QuestCoinReward{
rewardAmount = 100,
},
}
};
}
}
One potential solution is to create a new set of EventArgs, like this:
public class QuestCompletedEventArgs : System.EventArgs
{
public QuestObjective FinishedObjective { get; }
public QuestCompletedEventArgs(QuestObjective objectiveIn) {
this.FinishedObjective = objectiveIn;
}
}
(probably in a different file)
... and use it like this:
First, create an event delegate:
public delegate void QuestObjectiveCompleteHandler(object sender, QuestCompletedEventArgs e);
Instantiate the event delegate:
public event QuestObjectiveCompletedHandler CompletedObjective;
Define the method that will do something when the objective is completed:
public void ObjectiveCompleted(object sender, QuestCompletedEventArgs e)
{
// do something
}
Assign that method to the event:
this.CompletedObjective += this.ObjectiveCompleted;
From here, you can make the FinishedObjective object within the QuestCompletedEventArgs a List<QuestObjective>, and FinishedObjective.add(objectiveIn) whenever appropriate.
You should also be able to make the event handling method act differently when a certain amount of objectives have been completed, or whatever you want to do with that information.
Of course, you can also add multiple different methods to respond to this event by adding more this.CompletedObjective += this.methodName; lines, as long as the signature of the new method(s) carry that same signature.
Reading into your example, I have written up some code where "A" is QuestObjective and "B" is Quest. The Quest object needs to know when objective has been marked as completed.
Using event handlers, we can set it up so that B is notified when an action occurs on A.
Like this:
// B
public class Quest
{
public Quest()
{
Objectives = new List<QuestObjective>();
// load objectives... Fake
Objectives.Add(new QuestObjective("obj 1"));
Objectives.Add(new QuestObjective("obj 2"));
Objectives.Add(new QuestObjective("obj 3"));
foreach(var o in Objectives) // subscribe to QuestObjective events
{
o.ObjectiveCompleted += (sender, args) => ReportObjectiveCompleted();
}
}
public void ReportObjectiveCompleted()
{
// let 'em know
}
public List<QuestObjective> Objectives { get; set; }
}
// A
public class QuestObjective
{
public string Name { get; set; }
public QuestObjective(string name = "unknown")
{
Name = name;
}
public event EventHandler ObjectiveCompleted;
public void MarkCompleted()
{
// when a task is marked as complete and IF there are
// subscribers to this event then call the event handler
var a = ObjectiveCompleted;
if (a != null)
{
a(this, new EventArgs()); // use different event args to pass data
}
}
}
I'm trying to model blood flow. As simple as it gets I am trying to have an event (TimerTick) trigger the transfer of a resource object from unit A to unit B, B's to C, C's to A. I can't seem to have reproducible transfer of the resources from one iteration to another, and I've tried a lot more of different ways than what's pasted below.
The units are connected as a triangle after construction. I'm aware that this leaves the first unit to be erroneous, and have tried numerous ways to deal with it (and I'm not sure if that is the only problem) but since none have worked I won't post them.
An idea of event set-up where any one unit doesn't need to know about any others would be totally appreciated. But just a way to make this work would also be appreciated.
class Unit{
Resource currentResource;
Resource incomingResource;
Unit preUnit;
Unit postUnit;
public Unit( int resource, Timer t)
{
this.currentResource = new Resource(resource);
t.TimerTick += T_TimerTick;
}
private void T_TimerTick(object sender, TimerTickEventArgs e)
{
postUnit.receiveResource(currentResource);
currentResource = incomingResource;
}
void receiveResource(Resource resource)
{
incomingResource = resource;
}
//pre and post units connected appropriately to Cycle ...-A-B-C-A-...
// with one cycle per TimerTick
}
I would suggest Inversion of control with Observer pattern
Here is a working setup, to explain my suggestion. In my code, each unit just increments the resource by 1.
And gererates the following output
*Initiating Unit A value 1
Unit A, output 1
Unit B, input 1
Unit B, output 2
Unit C, input 2
Unit C, output 3
Unit A, input 3
Unit A, Cycle complete*
client code
IInitiatorUnit A = new InitiatorUnit("A");
IUnit B = new Unit("B");
IUnit C = new Unit("C");
B.RegisterUnit(A); // B is listening to A
C.RegisterUnit(B); // C is listening to B
A.RegisterUnit(C); // A is listinig to C
void heartBeatTimer_Tick(object sender, EventArgs e)
{
A.GenerateResource(1); // Start the process
}
I have designed unit such that it takes an input, processes it & fires an event for
interface IUnit
{
event EventHandler<ResourceGeneratedEventArgs> ResourceGenerated;
void RegisterUnit(IUnit inputUnit);
string Name { get; }
}
class Unit : IUnit
{
IUnit mInputUnit;
public event EventHandler<ResourceGeneratedEventArgs> ResourceGenerated;
public string Name { get; private set; }
public Unit(string name)
{
this.Name = name;
}
public void RegisterUnit(IUnit inputUnit)
{
this.mInputUnit = inputUnit;
this.mInputUnit.ResourceGenerated += mInputUnitResourceGenrated;
}
void mInputUnitResourceGenrated(object sender, ResourceGeneratedEventArgs inputResource)
{
//take the input resorce. pocess it & fire the event;
//I'm just adding 1 to it as sample
int outputResource = inputResource.Resource + 1;
Console.WriteLine("Unit {0}, input {1} ", this.Name, inputResource.Resource, outputResource);
OnResourceGenerated(outputResource);
}
protected virtual void OnResourceGenerated(int outputResource)
{
Console.WriteLine("Unit {0}, output {1}", this.Name, outputResource);
if (ResourceGenerated != null)
ResourceGenerated(this, new ResourceGeneratedEventArgs(outputResource));
}
}
public class ResourceGeneratedEventArgs : EventArgs
{
public ResourceGeneratedEventArgs(int resource)
{
Resource = resource;
}
public int Resource { get; private set; }
}
/// <summary>
/// This is just to start the process here, Nothing great here
/// </summary>
interface IInitiatorUnit : IUnit
{
void GenerateResource(int initialValue);
}
class InitiatorUnit : Unit, IInitiatorUnit
{
//prevents the cycle from going on & on
bool resetFlag;
public InitiatorUnit(string name):base(name)
{
}
public void GenerateResource(int initialValue)
{
resetFlag = false;
Console.WriteLine("Initiating Unit {0} value {1}", this.Name, initialValue);
OnResourceGenerated(initialValue);
}
protected override void OnResourceGenerated(int outputResource)
{
//Dont raise the event. if the cycle has completed
if (resetFlag == false)
{
resetFlag = true;
base.OnResourceGenerated(outputResource);
}
else
{
//do nothing, cycle has completed
Console.WriteLine("Unit {0}, Cycle complete", this.Name);
}
}
}
Edit 1 : Approach for very large number of units
In my 1st approach, each unit called the subsequent one creating a chain, something like this -
As the nodes would increase so would be the depth of call stack, therefore when the number reaches in thousands we can overrun the stack limit causing a stack over flow.
Therefore the new approach is a just uses an iterates over the units, giving each unit a request, getting a response back and feeding the response to the subsequent unit as request. Something like this -
Here is a working sample & it generates the following output -
Unit A, input 0
Unit A, output 1
Unit B, input 1
Unit B, output 2
Unit C, input 2
Unit C, output 3
Cycle completed result 3
Client Code
private void ClientCode()
{
// Create an array of units
IUnit[] units = new IUnit[] {
new Unit("A"), // A will be called 1st . It needs to be given an initial value to start processing
new Unit("B"), // B will get A's Output to process.
new Unit("C"), // C will get B's Output to process.
};
// pass the array to workflow to process
cycle = new WorkFlow(units);
Console.ReadLine();
}
Heart beat timer
void heartBeatTimer_Tick(object sender, EventArgs e)
{
var result = cycle.Execute(new Resource(0)); // starting the cycle with initial value of 0
Console.WriteLine("Cycle completed result {0}", result.Value);
}
The infrastructure is much simpler now,
interface IUnit
{
Resource ProcessResource(Resource inputResource);
string Name { get; }
}
class Unit : IUnit
{
public string Name { get; private set; }
public Unit(string name)
{
this.Name = name;
}
public Resource ProcessResource(Resource inputResource)
{
//take the input resorce. pocess it & fire the event;
//I'm just adding 1 to it as sample
int outputResource = inputResource.Value + 1;
Console.WriteLine("Unit {0}, input {1} ", this.Name, inputResource.Value);
Console.WriteLine("Unit {0}, output {1} ", this.Name, outputResource);
return new Resource(outputResource);
}
}
class WorkFlow
{
IUnit[] mUnits;
public WorkFlow(IUnit[] units)
{
this.mUnits = units;
}
public Resource Execute(Resource initiatingResource)
{
Resource result = initiatingResource; // initialise result with the input of the cycle.
for (int i = 0; i < mUnits.Length; i++)
{
// the result is passed as input.
//IUnit.ProcessResource function gives back a new result which is encached as input for subsequent resource
result = mUnits[i].ProcessResource(result);
}
return result; // after all are processed,
}
}
public class Resource
{
public Resource(int resourceValue)
{
Value = resourceValue;
}
public int Value { get; private set; }
}
Hope all will work fine now. Please write me a comment if any bit is unclear.
Are those elements and method named, Unit, within a class which is also named Unit?
Try moving the methods and elements to another class.
I would make three static classes for your units:
public static class UnitA
{
var foo1;
var foo2;
}
public static class UnitB
{
var foo1;
var foo2;
}
public static class UnitC
{
var foo1;
var foo2;
}
For each tic, you can handle the resources from any other class:
FooHandler_Tick
{
// Store C's content.
var tempFoo1 = UnitC.foo1;
var tempFoo2 = UnitC.foo2;
// B to C.
UnitC.foo1 = UnitB.foo1;
UnitC.foo2 = UnitB.foo2;
// A to B.
UnitB.foo1 = UnitA.foo1;
UnitB.foo2 = UnitA.foo2;
// C to A.
UnitC.foo1 = tempFoo1;
UnitC.foo2 = tempFoo2;
}
Does this work with your project?
I believe I have a design question and I hope to get your input. I made a small program to illustrate my question. Basically, my program consists of a radio system that gets heard on every room in the building. The sound is conditional on the receiving end, depending if the room registers itself to the radio system.
My problem is that the message sent is triggered on every room, even if the room is not registered. I would prefer to do the condition before the message gets sent out, rather then on the receiving end. By doing this, I could save myself unnecessary traffic. Can anyone give me an idea or the correct way to resolve this type of situation?
Just for the record, I would prefer not to have multiple event handlers in the radio, since I don't know how many rooms there will be.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Radio
{
#region Speakers
public interface ISound
{
string sound { get; set; }
}
public abstract class RoomSpeaker : ISound
{
public string sound { get; set; }
}
public class Room1Speaker : RoomSpeaker
{
}
public class Room2Speaker : RoomSpeaker
{
}
public class BuildingSpeaker : RoomSpeaker
{
}
#endregion
#region Rooms
public abstract class Room
{
public Radio radioPlayer;
public string name;
public HashSet<Type> registeredSpeakers = new HashSet<Type>();
public virtual void RoomPlayer(string lyrics)
{
registeredSpeakers.Add(typeof(BuildingSpeaker));
Console.WriteLine(lyrics);
}
}
public class Room1 : Room
{
public Room1(Radio radioPlayer)
{
this.radioPlayer = radioPlayer;
name = "Room1";
registeredSpeakers.Add(typeof(Room1Speaker));
radioPlayer.onRadio += radioPlayer_onRadio;
}
// This is what I don't think I like. It will only do something if it's registered. That's fine.
// But on any radio message out, this room will get called regardless. Should I NOT be doing this? Should I go back to
// making an eventHandler for every room? rather then having one even handler for all the rooms and have a condition on the receiving end.
void radioPlayer_onRadio(object sender, ISound e)
{
if (registeredSpeakers.Contains(e.GetType()))
RoomPlayer(name + e.sound);
}
}
public class Room2 : Room
{
public Room2(Radio radioPlayer)
{
this.radioPlayer = radioPlayer;
name = "Room2";
registeredSpeakers.Add(typeof(Room2Speaker));
radioPlayer.onRadio += radioPlayer_onRadio;
}
void radioPlayer_onRadio(object sender, ISound e)
{
// same problem as in Room1.
if (registeredSpeakers.Contains(e.GetType()))
RoomPlayer(name + e.sound);
}
}
#endregion
public class Radio
{
public event EventHandler<ISound> onRadio;
public void PlayRoom1()
{
onRadio(this, new Room1Speaker() { sound = "Test" });
}
public void PlayRoom2()
{
onRadio(this, new Room2Speaker() { sound = "Test" });
}
public void PlayAllRooms()
{
onRadio(this, new BuildingSpeaker() { sound = "Test All Rooms" });
}
}
class Program
{
static void Main(string[] args)
{
var radio = new Radio();
var room1 = new Room1(radio);
var room2 = new Room2(radio);
radio.PlayRoom1();
radio.PlayRoom2();
radio.PlayAllRooms();
Console.ReadLine();
}
}
}
Okay, what you're looking at is the publish-subscribe pattern (AKA eventbus). In eventbus pattern, you have a class that registers listeners and sends messages. Listeners tell the event bus "I'm listening for an event X". When the eventbus "sends" event X it consults its list of listeners for that event and if they are registered, executes the method that the listener told it to execute.
public class EventBus
{
private Dictionary<Type, List<Action<IEvent>>> actions = new Dictionary<Type, List<Action<IEvent>>>();
public void Listen<T>(Action<IEvent> callback) where T : IEvent
{
if (!actions.ContainsKey(typeof(T)))
{
actions.Add(typeof(T), new List<Action<IEvent>>());
}
actions[typeof(T)].Add(callback);
}
public void ClearCallbacks<T>() where T : IEvent
{
actions[typeof (T)] = null;
}
public void Send<T>(T #event) where T : IEvent
{
if (!actions.ContainsKey(typeof(T)))
{
return;
}
foreach (var action in actions[typeof(T)])
{
action(#event);
}
}
}
public interface IEvent
{
}
Usage:
public static void main () {
var eventBus = new EventBus();
var aRoom = new NoisyRoom(eventBus);
var bRoom = new NoisyRoom(eventBus);
var cRoom = new NoisyRoom(eventBus);
var dRoom = new QuietRoom(eventBus);
eventBus.Send(new NoisyEvent()); //sends to a,b,c room
}
public class EasyListeningEvent : IEvent
{
}
public class QuietRoom
{
public QuietRoom(EventBus eventBus)
{
eventBus.Listen<EasyListeningEvent>(BringTheNaps);
}
private void BringTheNaps(IEvent #event)
{
//its been brought!
}
}
class NoisyEvent : IEvent
{
}
public class NoisyRoom
{
public NoisyRoom(EventBus eventBus)
{
eventBus.Listen<NoisyEvent>(BringTheNoise);
}
private void BringTheNoise(IEvent #event)
{
//its been brought!
}
}
Try something a little more like this:
Edit: Note that this is just a start in the right direction. You can take this a lot further. Basically what you have is a sound source and a sound emitter. Obviously a radio is a sound source, and a speaker is a sound emitter, but something like a room could be both. A radio should not know what a speaker or a room is, it should only know about emitters, and it should only send sounds to them. Based on this, a room should have a collection of emitters (which would probably be speakers), and when a room gets a sound from a radio, it would simply relay that to whatever emitters it has registered. There would also be nothing stopping you from registering speaker directly to a radio. This code should help show how you might implement all of that.
public class Radio
{
private HashTable<string, EventHandler<ISound>> rooms = new ...;
public void RegisterRoom(string room, EventHandler<ISound> onSound)
{
rooms[room] = onSound;
}
public void UnregisterRoom(string room)
{
rooms.Remove(room);
}
public void PlayRoom(string room)
{
EventHandler<ISound> onSound;
if (rooms.TryGetValue(room, out onSound))
{
onSound(this, new BuildingSpeaker() { sound = "Test" });
}
}
public void PlayAllRooms()
{
if (rooms.Count == 0)
{
return;
}
var speaker = new BuildingSpeaker() { sound = "Test All Rooms" };
foreach (var room in rooms)
{
room.Value(this, speaker);
}
}
}