A blackboard is an object that stores and fetches generic key-value pairs at runtime. It could be implemented by a Dictionary<object,object>
. Some subsystem writes to the blackboard to let other subsystems read from it.
The system storing the blackboard has no idea what type of object is in it, and when. The writers and readers to a particular key always know and agree on the type of the key-value pair. Compile-time checking is sacrificed for ease of implementation - there are numerous writers and readers and they are constantly iterated on.
My blackboard interface looks like this:
interface Blackboard {
bool HasKey(object key);
T GetValue<T>(object key);
}
Writers create and return blackboards, so SetValue(key, value)
can be an implementation detail.
My initial implementation used Dictionary<object,object>
and everything was fine. However, this blackboard must be quick and alloc-free. This is non-negotiable. If a writer pushes a float value into the blackboard, the naive implementation boxes the int to put into the dictionary.
I can't use a generic type on the implementation class, BlackboardImpl<ValueType> : Blackboard
, because the value type is not constant across blackboards.
I can use multiple internal dictionaries, Dictionary<object,float>
, Dictionary<object,int>
etc with a fallback Dictionary<object,object>
, and many SetValue functions, so now I don't box on insertion. However since the GetValue function comes from the interface, I can't put constraints on it, so I'm still boxing on exit:
T GetValue<T>(object key) {
if (typeof(T) == typeof(int)) {
// return intStore[key]; // doesn't compile
return (T)(object)intStore[key]; // boxes, allocates, bad.
}
// ...
}
Is there any syntactical trick I'm missing here, including changing the blackboard interface, to avoid boxing? Any reflection hacks are going to violate the "quick" requirement, even if you can implement it without allocations.
Cheers.