I had a bit of trouble phrasing this as a question for the title. I'll be as concise as I can without missing anything.
Say you have a class which serves as a component in a large data structure of these components.
Each component can have one or more 'inputs', and zero or more 'outputs', both of which are just other instances of the same class. A component is meant to perform an action once all inputs have notified it that they have performed their actions.
class Foo
{
void checkIn()
{
// if all inputs have checked in, takeAction() and then checkIn() with outputs
}
void takeAction()
{
}
}
This isn't a strict hierarchy, and there can be diamond dependencies like so:
A
/ \
B C
\ /
D
where B and C both depend on A, and D depends on both B and C. D could even take A, B, and C as inputs. This isn't the crux of the question, but I'm pointing it out because if it were a simple hierarchy there wouldn't be an issue.
There will be a multitude of these components, and this will be used in a real-time simulation scenario, so time efficiency is of paramount importance. Space efficiency is also important, but not as much as time.
Lastly, a component doesn't need any information from it's input aside from knowing that it's completed its task. It doesn't need a pointer to the inputs, or access to it's data, and most often, their relationship exists only because they are both operating on data external to both of them which needs one or more operations to complete before moving onto the next.
Currently, my solution is to have each component only store the total number of inputs needed to proceed.
void checkIn()
{
checkInCount++;
if(checkInCount >= totalCheckInsNeeded)
{
performAction();
// checkIn with outputs
checkInCount = 0;
}
}
This works for efficiency and space, but I don't like that there's no accountability for the inputs. The design is of course meant to make sure that while the data structure is being initialized and the components are getting created and connected that all checkInCounts are correct, and there could never be a situation where an input ends up checking in twice (and thus reaching the totalCheckInsNeeded before all inputs have checked in). I just don't like that if there is a bug in that initialization process, it could be really difficult to track down in a large structure with potentially hundreds of thousands of components.
Any solution I've thought of that mitigates this accountability issue would involve something along the lines of checking against a list of inputs, which on the surface sounds vastly less efficient for both time and space. I would just add in the list of inputs solution for debug builds only, but in the event there's an unknown bug, I'd rather have a way to detect an issue and fail gracefully rather than just have the program chug on oblivious to it's broken state.
I'm hoping there's either a design pattern I hadn't considered, or a language feature of C# that I'm not yet aware of. (Relatively new to C# from a C++ background, and still learning the ins and outs)
As you main concern is multiple checkins of a single input, you may use the approach with a bit mask instead of a counter. Trivially you may use int32 or int64 that limit you to 32/64 inputs. As an input is done it clears a corresponding bit in the connected component. If it tries to clear already cleared bit, you have an error situation. As all bits are zero, it means all inputs were processed, you can trigger an action. The 32/64 limitation can be easily overridden by using a byte array if you need it. There's one difficulty - how to define a corresponding bit? You may keep a bit number in the input element. What if this input is connected to several components? It should have the same bit number in all connected components. If you can guarantee that you can set all bit numbers in this way without conflicts during model creation process that this will be the simplest solution.
A B A must have the same bit number in QWR, B in RTE
/|\ /|\ A's and B's bit number should not conflict in R
Q W R T E
A little more memory expensive way is to assign individual bit numbers to each link. It means you should have a byte array of size in each input. 3 bytes in A in this example.