2

I'm writing an application where I need to simulate the distribution of a resource such as electricity or gas among a large network of objects connected to each other in arbitrary ways. No physics need to be simulated. Just the flow of the units of resources when objects ask for them.

Consider the below diagram:

ObjA <---> ObjB <---> PwrA <---> ObjF
            /\                                ObjG
   ObjE<---/  \---> PwrB

The Pwr objects can provide resource electricity when asked by the Obj objects. What I need to simulate here is that if ObjA needs 50 electricity it should send a message to all connected peers without caring who it goes to and should get the resource back only from peers it is connected to directly or indirectly.

I'm not simulating latency here or anything so transmission will be instant. From a programming point of view this is all entirely local within the application (No real networking or anything like that).

My challenge here is trying to program this in a modular and clean way. I'd like to avoid giant lists of locally connected nodes and large ifelse statements about how to deal with messages if possible.

I want to both achieve my goal and also learn something new. I've been reading articles and books and programming concepts for a few weeks now and one that I think might be a fantastic solution is C# Delegates.

I've done some prototyping and managed to setup objects with a MessageOut Delegate that other connected objects can subscribe to and listen for outgoing messages to deal with.

This works quite well but I have the following short-comings due to my in-experience with the concept:

  • I don't know how to deal with circular references with message passing between connected objects: In the above diagram if ObjB broadcasts a message for electricity ObjA, PwrA, PwrB, ObjE will be notified, which will in turn all process and then notify ObjB. Which will in turn notify...etc. The way networking deals with this is to simply broadcast out all ports (Objects listening to a delegate) except the port that received it. How can I implement this with delegates? A flat broadcast won't work.
  • Listening to a delegate effectively creates one-way communication. To implement two-way communication I'd need to have objects listen to outgoing messages for each other. But without a above to the above its another circular reference surely?
S.Richmond
  • 11,412
  • 6
  • 39
  • 57
  • You'll have to describe/design the notifying better. If every item starts broadcasting when it receives something you can eliminate the cycles but it would still be O(N!) – H H Apr 03 '13 at 12:38
  • I've removed the delegate-only solution scope as it appears much better solutions are available. – S.Richmond Apr 03 '13 at 12:40

4 Answers4

3

Delegates would be unwieldy and messy in my opinion.

Have you looked at TPL Dataflow? http://msdn.microsoft.com/en-gb/library/hh228603.aspx

It provides a number of "blocks" that have behaviour such as queuing requests, sending requests etc, you could use it to simulate the push and pull of these virtual resources.

All the magic for connecting them together and dealing with circular dependencies is all taken care of.

Clint
  • 6,133
  • 2
  • 27
  • 48
  • Very interesting! Unfortunately I may have some dependencies around the required .NET 4.5 version. But if this is a good solution then I may look into the effort required to go that way. One key question I have so far in my read is: If I connect 2 power objects to a single object and it asks for power, both power nodes will receive the request or message in the TPL pipeline. How do I handle which node the object should take power from? Can I have it 'load balance' the power like real electricity would? – S.Richmond Apr 04 '13 at 13:37
  • @Clint I've finished reviewing TPL Dataflow and it seems perfect! Unfortunately I'm stuck on .NET 3.5 or under and as far as I know TPL Dataflow can't target that framework. I'm going to mark your answer as correct however I'd love to get any further advice from you on other options (If any). – S.Richmond Apr 06 '13 at 02:14
  • @Clint That article is wrong (at least for the current version of TDF). There is no `Greedy` property on [`DataflowBlockOptions`](http://msdn.microsoft.com/en-us/library/system.threading.tasks.dataflow.dataflowblockoptions.aspx) (there is one on [`GroupingDataflowBlockOptions`](http://msdn.microsoft.com/en-us/library/system.threading.tasks.dataflow.groupingdataflowblockoptions.greedy.aspx) but that's not useful here). Though you can get a very similar behavior by setting `BoundedCapacity` of the target blocks to some small number. – svick Apr 06 '13 at 10:36
  • @svick I've recently come to the same conclusion. But unfortunately I find that once the capacity is filled those ActionBlocks no longer accept messages, which I believe is by design. How can I resolve this? – S.Richmond Apr 07 '13 at 11:18
  • @S.Richmond you could always write your own block class that accepts unlimited messages, the MSDN articles have information on subclassing existing blocks and on implementing the necessary interfaces. – Clint Apr 07 '13 at 11:33
  • 1
    @S.Richmond That is by design, because that's how is the load balancing achieved. They will accept messages again once their output queue is emptied out. If you don't want that, you can add an unbounded `BufferBlock` after each bounded `ActionBlock`. – svick Apr 07 '13 at 11:54
  • @svick How does one empty an ActionBlock's output queue? – S.Richmond Apr 07 '13 at 13:08
  • @S.Richmond by having another block connected to it consume its output. – Clint Apr 07 '13 at 13:15
  • @S.Richmond I'm an idiot. Of course `ActionBlock`s don't have any output queue since they don't produce any output. What this means is that you don't have to do anything to empty it out. So a bounded `ActionBlock` will accept messages again once it processes the current message(s). – svick Apr 07 '13 at 13:16
  • @svick Unfortunately my testing thus far has proved otherwise. The code here: http://codingndesign.com/blog/?p=212 will not work with bounded capacity in place of non-greedy. It appears to forfill the capacity on the ActionBlock's and then refuse the rest of the messages. – S.Richmond Apr 07 '13 at 13:26
  • @svick Still doesn't work for me. But I strongly believe that is because of the underlying Dataflow or TPL code I'm using from the Mono project and compiled against .NET 3.5. I'm not sure how much further I can go with Dataflow - I don't want to be running on unsupported code like this under .NET 3.5. – S.Richmond Apr 07 '13 at 14:11
1

If you're working with events, you will notice the best practice is to have a "sender" object and an "EventArgs" in every event delegate.

I had to do something like that once, and the solution that came up was adding the "OriginalSender" into an EventArgs derivate class.

Every received message passes first into an if statement:

class MyEventListenerClass
{
     .....
    private void EventHandlerMethod(object sender, MyEventArgs E)
    {
        if (E.OriginalSender != this)
        { 
            do what is needed
        }
        else
        {
            got this message from myself in a circular way....discard it
        }
    }
     ......
}


class MyEventArgs : EventArgs
{
    public object OriginalSender {get; private set;}
    public MyEventArgs(object OrigSender) : base ()
    {
        OriginalSender = OrigSender;
    }
}

But that doesn't solve everything. In my case, there weren't any loops not containing the original sender. If the messages fall into a loop like that, it will be infinite. So maybe a list of senders could solve it, making if (E.SenderList.Contains(this)) {...}. So every time a message is received, the receiver checks if the list contains itself. If not, he sends the message adding itself into the senderlist.

Daniel Möller
  • 84,878
  • 18
  • 192
  • 214
0

Seems that you in the right track .. it is actual lay called the observer/listener design pattern ... and you should use Events in C# to better implement that pattern. Events are basically a type of delegate you can find more info in MSDN or Google...

With regards to the circularity problem:

You shield just pass a list of senders along with the message .. and each object that listiens will have to check if he was one of the senders i n that list and decide weather he should transmit a Massey as well or not.

Mortalus
  • 10,574
  • 11
  • 67
  • 117
0

I don't think that delegates are a good solution for the problem you presented. Lets take a look at the following scenario:

ObjB needs 1 electricity unit, now, let's assume that it have a delegate called "GetElectricity", as i understand from what you wrote, PwrA, PwrB have both added an handler for the delegate, both PwrA and PwrB handlers will be invoke, or you will try the first handler, if it didn't provided that electricity needed it will try the next one... this will create a complex, hard to maintain software.

what you are talking about (or at least, what i would do :)), is Events, ObjB will raise an event for NeedElecricty, all the Pwr objects have register for it, if they can provide elecricity they will call an event "ProvideElectricity" on ObjB, than ObjB can decide from which one of them he will take the power he needs, and inform them that he needs 1 power unit from PwrA (and will tell PwrB that he doesn't require it services).

look for Observers and Events, as it is what you are mainly talking about.


I agree with @Clint, the TPL dataflow is the best solution, as it does exactly what you are talking about. but as you said it is out of scope.

Nadav Ben-Gal
  • 549
  • 3
  • 13