If we could keep this forum topic specific to the engine / AI interface then things won't get so messy. I think this talk about lobby development is useful, but maybe a different thread would be better, since it's a different issue, and there is still plenty to clear up here.
Hoijui's done a lot of work specing up some code for us all. I think we need to discuss it a little:
hoijui wrote:what do you think?
Most of it is good, but there are a few things that I think aren't quite right. This is what I think the interface should look like:
IAIInterface.h
Code: Select all
#ifndef IAIINTERFACE_H_
#define IAIINTERFACE_H_
DLL_EXPORT int interfaceVersion();
DLL_EXPORT int handleEvent(int teamID, int messageID, void* message);
#define NULL_EVENT 0
#define INIT_EVENT 1
#define UPDATE_EVENT 2
// ...
struct SNullEvent {
};
struct SInitEvent {
int myTeamId;
AIContext aiContext;
};
struct SUpdateEvent {
int frame;
};
// ...
#endif /*IAIINTERFACE_H_*/
IAIInterface.cpp
Code: Select all
DLL_EXPORT int handleEvent(int teamID, int messageID, void* message) {
switch (messageID) {
case NULL_EVENT:
// The coder does what they want here!
// See example for UNIT_CREATED_EVENT
break;
case INIT_EVENT:
// ...
break;
case UPDATE_EVENT:
// ...
break;
case UNIT_CREATED_EVENT:
// A simple way to unpack the information is like this:
// SUnitCreatedEvent e = (SUnitCreatedEvent) message;
// A slightly better way is to create an object with
// the appropriate information, and then we can use methods
// as we wish, like this:
UnitCreatedEvent event(message);
eventHandler[teamID].handle(event);
break;
default:
// Maybe we want to log a warning here.
break;
}
}
Where eventHandler is an array of class EventHandler, with an EventHandler for each team handled by the AI. EventHandler would be something like:
Code: Select all
class EventHandler {
void handle(Event event);
}
and we can use full type checking on the Event called event, with polymorphism on our side. This means we can do clever things like making UnitCreatedEvent a subclass of Event, where Event has the following spec (or anything similar):
Code: Select all
class Event {
public:
virtual void execute();
}
The great thing is that now in the handle method of the EventHandler we can execute the Event if we wish, or could choose not to. We can do plenty of other things in the handle method, like logging events for debugging and profiling, etc. We could even call interfaceVersion() to make sure we have the version we support.
Notice that we first had to create structs SUnitCreatedEvent. Why? Because we can't pass C++ type classes around in our API, but we can pass a struct as data that will then fill in the body of the C++ class with the type UnitCreatedEvent.
The point is that an AI developer can choose to do this if they want: I don't think we should burden the current spring developers with these details since they have better things to worry about. If we all want a common way of doing things past the interface that's fine -- but it's a different concern, and the AI devs should do it amongst themselves. (I'm happy to do the maintenance for this, but we'd need clearance from the spring devs).
I won't bore everybody with the details of handleCommand, since it's exactly the same game but on the other side.
One thing we still haven't sorted out is the callback for the getState().
Tobi wrote:
However, this is contrary to what everyone wants, and I also think it's over-design/over-generalization.
So, I think the quicker solution to make functions like float GetUnitHealth(int unitid) etc. is the way to go.
I'm inclined to agree with Tobi here. I know that AF has concerns that we're not being generic enough, but I guess at some point we need a concrete set of states that we can demand from the engine.
The only suggestion I have is something like this:
But Tobi has already mentioned the problems with this approach:
Tobi wrote:A generic callback function seems like a useless complication to me for the callback interface, not in the least because of the trouble of returning a pointer to an object:
* it can't just be created on the stack in the engine because then pointer would be dangling on return of the callback function.
* It can't be allocated on the heap without a bunch of code to automatically garbage collect it a later frame.
* It can't be allocated on the heap and freed in the AI because they may use a different C runtime (ie. different heaps).
* It can only be made a global variable engine side (or member of the object representing the AI, but that's still pretty much global), with accompanying issues of people still trying to free the pointer, callbacks being non-reentrant, etc.
These are serious issues, but I think we need to do things this way because we gain the following:
* granularity is no longer an issue: the engine can be as course or fine as the AI needs it to be.
* complete forward/backward compile compatibility between engine and AI.
* future extensions trivial to create.
* generic interface means the engine is *completely free* to change internal representations (so long as it provides appropriate adaptors).
The trouble is in designing appropriate callback IDs and structs, and of course the dangling pointer problem, which is the more serious concern.
I think the dangling pointers can be avoided if we use a struct like:
That is only guaranteed to exist and be correct between its initial callback, and the next call to callback(). This is
dangerous if the AI dev doesn't know what is going on, and is why I'm not 100% sure this is correct.
Of course, we'd also need UnitDef, and other fundamentals, but these would be nothing more than passing these (constish) structs, with a callbackID of UnitDefState.
Let's get back on track to answering this one question since it's the last theoretical aspect we need to think about before code can be rolled out:
What is the best way of implementing getState()?