Can a function accept an unspecified data format as its argument, instead of a specific one?

This might be a really silly question, but I’m trying to write a generic event messaging system, where every function in my game from attacks to drinking potions is notified through some function NotifyEvent(EventID, EventData). This gets sent to every component on the character/item/door/whatever, the idea being that items which have specific functionality for that event will be activated and the rest will ignore them. So if I run NotifyEvent(“Attack”, data), the sword I’m holding will play its swinging animation, but my armor will completely ignore it.

That having been said, different verbs require vastly different amounts and types of data. A weapon might want to know which enemy it’s attacking, a vehicle might need an axis value to read how hard you’re stomping on the gas, and consumables will want a callback to the character who consumes them. Is there an argument I can receive in my main method which will let me pass it any type of data or struct, and leave me to do my type safety checks inside each specific implementation?

If you passed a generic type through, you’d need to know what type to cast it to within the function (which I can assume you’ll be able to with your EventID).
I don’t know the best method for that, but you could potentially try overloading the function instead.



void NotifyEvent(FString EventID, Fstring EventData);
void NotifyEvent(FString EventID, FVector EventData)
void NotifyEvent(FString EventID, int EventData)


I know it kinda stinks to have similar logic in every function with just different parameters, but it might work for your case.

You want to create template functions: template<T> Void Blah(…) { }
They are kinda complicated to build and debug messages are weird so most ppl don’t like them, going with overloading instead.
Here’s some tips from the Wiki about templates in gameplay code…

Hmm, that would work… this is a half-formed idea, but is there any way to make one TMap hold multiple data structures? Because then it would be trivial, you could just pass the event ID and a tkey, and trust that some DataMap dictionary was correctly curating the necessary information.

Edit: Okay, so templates: is the idea that I create one template for each possible input, then when the function gets called, it’s intelligent enough to go “Okay, let’s compare the passed method to the templates I have, and figure out if there’s a type-safe template I can hand to whoever’s running this”?

Templates are a compile time construct. From the way I read your question, I think you need a more dynamic solution.
I would define some base class for your EventData, and pass in a pointer to that base class along with your EventID. If you need blueprint access, your base class should be a UObject:


UCLASS()
class UEventDataBase : public UObject
{
  GENERATED_BODY();

  // Any data common to all events, if any, should go here
};

Or if you will only need to manipulate this stuff in C++, it will be faster to just use a standard class/struct:



struct FEventDataBase
{
  // Any data common to all events, if any, should go here
};

Then you subclass the above type for each different event type you have, adding more specific data members.
Finally in your objects implement as follows:


void MyObject::NotifyEvent(IDType EventID, F(U)EventDataBase* EventData)
{
  // Here you test the event type (using a switch perhaps), and if relevant, you cast the EventData argument to the type associated with the event type
  // eg. FSwordEventData* SwordEvent = static_cast< FSwordEvent* >(EventData);
  // or USwordEventData SwordEvent = Cast< USwordEventData >(EventData);
}

Having said all that, if you’re going to have a lot of objects and a lot of events, you probably want to rethink your overall design. By sending all events to all objects and letting them decide whether or not they want to do anything, you’d end up with a whole lot of virtual calls and switch statements leading to doing nothing at all. Not very performant.

Hmm, I think you’re right about the multiple function decs being the cleanest way to handle the data needs.

I know the way I’m doing this isn’t the most performant, but I’m reasonably sure it won’t ever actively impede performance. The way I’m doing it, every object that can hear these events has a listener component who in turn has a reference to every component that cares about these calls. I message the listener, and it’s in charge of calling FireEvent on each component that cares. Most actors that use this system have three or fewer objects that get the message, and none of the uses fire very frequently, so at least for now I’m pretty sure the flexibility it buys me outweighs the lack of optimality.

You might want to look at FVariantData as a generic data container type that you could then use. So have a “Message” which holds a hashed ID value and an array of FVariantData values. Look up the values by name and coerce the values when you use them.

There’s a trade off here between speed and flexability, so it depends on the use case for messages. Ones that are relatively rare could probably be OK with the overhead of the FVariantData. I believe FVariantData is used by the online subsystems to allow for varying data types to be sent/received from a server.

Phil.

Alright, I’ll look into it and do some benchmarks… thank you very much for the background :slight_smile: