I have finished implementing a working version of the new interface.
Everything is as we have discussed, except the callback mechanism which is exactly the same as before.
I have not yet implemented any of the creg functionality, so don't expect to be able to load/save games that use this AI interface.
The new interface does not support the work done for JAI either, but I expect that's not such a problem, since we would change the way that works later anyway.
I would say this is still a beta interface: I've taken a few shortcuts in the AILibraryGlobalAI files; but these are done since I anticipate that the GlobalAIHandler stuff will change if this interface is accepted, and I wanted to have minimal changes to existing code.
I have tested the new interface by creating a new AI called NullAI, which does nothing, other than load. I have also tested the new interface by porting the interface to RAI-0.533, and testing that, and as far as I can tell (I've just finished playing a game vs RAI), everything works fine.
The good thing about this new interface is that all that needs be done is to drop a few new files into RAI, and everything works fine; no part of RAI needs be changed. (Although I renamed the set ais to oldais, since ais is the right name for the ais mapping, and it will replace the current ais if everybody is happy). Porting this across to other AIs might require as few as 2 lines to be changed: it's that generic.
I'm not sure what the best way of distributing these changes are, since I don't have SVN access, and can't create a fork to show you (would it be possible to have this set up?) I have therefore included a patch file, patched against SVN version 6072, which is the latest at the moment.
I have commented most of what I have done in the code itself and hopefully what I'm doing should be very obvious.
Here's a note (you'll find in the code) to put AF's mind at ease about the init() function I export:
If we do not have an init() method, then we would instead pass an event InitEvent to handleEvent. However, we would have to make handleEvent have to wait for an InitEvent as a special case, since the team in question would not yet exist.
Therefore, the handleEvent code would look like this:
Code: Select all
DLL_EXPORT int handleEvent(int team, int eventID, void* event) {
if (eventID == INIT_EVENT) {
ais[team] = new CAIObject();
}
if (ais.count(team) > 0){
// allow the AI instance to handle the event.
return ais[team].handleEvent(eventID, event);
}
// no ai with value, so return error.
else return -1;
}
Advantages:
* All events to AI go through handleEvent.
* People don't get confused and start adding bananaSplitz() functions.
Disadvantages:
* We need to check for INIT_EVENT before *every message*.
* These events will happen only once per game -- after that the check becomes a necessary waste of time.
* Handling events is no longer about getting the right object to deal with an event, it also includes initialising object properly.
I understand that we want to keep the interface simple. In fact, I think we should keep it as minimal as possible, and ideally everything would go through handleEvent. Practically though, it does not make sense to do this: we'll be wasting our own time for no good reason in the case of initialisation.
The (in my opinion much cleaner) alternative is the one I've implemented.
Advantages:
* One function that initialises a team before everything is passed to handlEvent
* No redundant if statements.
* Simple design.
Disadvantage:
* People might start adding other functions to the interface.
I don't think that the disadvantage is a real one: it's pretty standard to see initialisation as a special case. It's pretty clear that everything else goes through handleEvent. I do understand that we might have problems with people adding extra functions, but this really ought to be an exception.
The advantage is clear: a more efficient, simpler design. Of course, you could argue that the efficiency is nominal, one extra if per event is very little cost, and granted, that's true; but this doesn't change the fact that we're checking for a special case that we know only happens once at the beginning of the game, before every single event after.
Of course, we still need an INIT_EVENT, since initialising the existance of a team member is not the same as initialising its state. (It's for this reason that
I would consider renaming init() to create()).
You might also argue that we do this check in the handleEvent switch within each team. This argument doesn't apply here because the team has yet to be initialised.