Interface Redesign - Page 8

Interface Redesign

Here is where ideas can be collected for the skirmish AI in development

Moderators: hoijui, Moderators

Tobi
Spring Developer
Posts: 4598
Joined: 01 Jun 2005, 11:36

Re: Interface Redesign

Post by Tobi »

For C# I think making a small C++ AI that allows interproces communication (ie. TCP over loopback) and has a way to start a process is way easier then integrating either mono or the CLR directly into an AI DLL.

EDIT:
Some advantages:
  • AI can just use WinForms or WPF or whatever for realtime debugging windows
  • Debugging the C# code with express edition of VS and/or MinGW compiled Spring would actually work
  • Spring memory corruption can not crash the AI
  • AIs compiled for different .NET runtimes can be combined in a single game (it's impossible to load different .NET runtime versions in same native application; that's also the reason windows shell extensions need to be written in C++)
  • AI would run in different thread (process) automagically, no need to think about threading issues etc.
Some disadvantage:
  • Getting unitdef etc. will be somewhat slower because data actually has to be copied a few times.
  • AI may lag a bit behind engine.
  • Everything AI does is asynchronous, makes certain things a bit harder.
zenzike
Posts: 77
Joined: 12 Apr 2008, 13:19

Re: Interface Redesign

Post by zenzike »

Agon wrote: What about the dll/jar loading code?
I really don't know, but would you mind opening a new thread if you want to discuss this? This thread is already full of stuff as it is, and I'd like us to stay focused on what we're doing. Thanks!
User avatar
AF
AI Developer
Posts: 20687
Joined: 14 Sep 2004, 11:32

Re: Interface Redesign

Post by AF »

Zenzike if we add save and load commands etc then the whole thing will spiral out of control, well have update functions and bannanSplitz() functions, people will say "well youve got save and laod and init so I just added my own function along with them"

If you want a prime example of this happening I point you towards the current interface which shows this effect in action.

Its just inconsistent ontop of all of that.

There's no reason why we cant send a save init or load message to the AI anyway.
zenzike
Posts: 77
Joined: 12 Apr 2008, 13:19

Re: Interface Redesign

Post by zenzike »

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.
Attachments
aiinterface.txt
This is a patch file, to be applied to the root of spring svn 6072.
(84.09 KiB) Downloaded 113 times
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Interface Redesign

Post by hoijui »

nice! :-)

i had to change the diff file, the file paths start with /, which does not work for me on windows. i also removed the spring/ in front of them, as i only have trunk checked out, and it is not in a directory named spring on my system; that is all i changed (file attached). to apply the patch, do this:

Code: Select all

cd <directory where you have springSVN6072>
patch -p 0 < aiinterface.txt
for most of you, this was obvious maybe. for me not ;-)

some questions:

load and save (events)
i think creg is not needed for AIs. if i did not oversee it, you did not yet implement the load and save functions, i guess because it is unclear how to do it, as the currently used iostreams are not portable to other languages, and most AIs do not currently support saving anyway, as much as i know.
could we use files? other suggestions?

NullEvent?
it is ok to me that you wrote the code, before we finished discussing this (and other) issue(s). but i want to go on discussing them.
what do you think about my argument (that NullEvents will be filtered out before the C interface)?
(this is part of the interface, and therefore the discussion belongs here, not to PMs or an other thread)

float3
why are you using this in the S*Events? i think it is really bad for compatibility/simplicity/obviousness of the interface.

init()
your argument(s) make only sense if an object oriented language is used. you make an exception to prevent an exception ;-). i could live with it though, as in practice most AIs will be using OO languages. you say: renaming init() to create(): do you mean, we will have two separate events then?

Code: Select all

create() // something like a constructor
init() // the initialization, which is invoked after the create()
like this? if so, why?
either way, if we really use a function for init, we should have a INIT_EVENT specifically for it, so we can convert it to an event for non OO languages (i think you had an other idea for INIT_EVENT).

maybe it would be good if some of us could have a chat once. i think this would be far more productive then this way of discussing about small things. would you be up for that?
Attachments
aiinterface.txt
removed "/spring/" on all filenames, eg:
/spring/rts/ExternalAI/AILibrary.cpp ->
rts/ExternalAI/AILibrary.cpp
(82.8 KiB) Downloaded 14 times
zenzike
Posts: 77
Joined: 12 Apr 2008, 13:19

Re: Interface Redesign

Post by zenzike »

load and save (events)
Nope, no loading or saving done at this point. I wanted to see if everybody agrees that this interface is (more or less) what we want, before going into details. I haven't given this any thought whatsoever; it's probably one of the last things we'll look at.

NullEvent
I suppose we could filter out the NullEvents, but I'm not sure if it's worth the check: we will very rarely need them so checking for one before every send seems a little over the top. I don't mind whether we do or don't filter them, so long as they do exist!

float3
I thought these were pretty obvious structs? Does anybody else have a problem with them?

init()
Gosh. I thought these days everybody agreed that for projects like these OOP principles were the best ways of doing things, and not "exceptional". I expect any well designed AI will be using some sort of OOP paradigm, and so I'm building to that spec!

No I don't mean having init() and create(), I only want us to have 1 extra function called either init() or create(). I chose init() because that's what we're used to seeing, although some might argue that create() is a better name. Either will do, but I think having it is a good idea. You'll notice that in RAI I use both the init() and the handleEvent(INIT_EVENT) ways of doing things: there's a different purpose for both; see the code. The first is to create a binding between the engine and RAI, the second is to initialise with data.

I'm happy to chat. I use skype, PM me and I'll give you my account name.
Last edited by zenzike on 25 Jun 2008, 22:36, edited 1 time in total.
imbaczek
Posts: 3629
Joined: 22 Aug 2006, 16:19

Re: Interface Redesign

Post by imbaczek »

why not have another function (other than handleEvent) do the initialization if that's such a special case?

oh drat, i'm late to the party it seems.
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Interface Redesign

Post by hoijui »

init()
i looked at the code now.
the code that initializes the AI, on the engine side, looks conceptually like this:

Code: Select all

initAI(aiName, team) {
   aiName.init(team);
   aiName.handleEvent(INIT_EVENT, InitEvent(team, callback));
}
if one would use C (or any other language which has no class system built in), and use OO principles, init() is not needed (as my draft shows). same for functional or logical languages. in this case, we can ignore init(), and only use INIT_EVENT, so it does not really make anything worse for non OO languages, the way you have it. nice :D

float3
with this, we export engine internal logic which could change in the future through the AI interface. we once said we will only use primitive data, cause it is easier for other languages. the interesting stuff of float3 is x,y,z. float3 is only useful for C or C++, where it eases up the work a bit. for all other languages, it is more complicated, if the struct is used. also, we will end up using other structs, eg UnitDef, which have tons of connected other structs. i would prefer if the AI would only have to include files from ExternalAI.
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Interface Redesign

Post by hoijui »

@Tobi
zenzike and me had a chat, and we have some questions.

init() - solved
for both of us, it is ok to have init() as a function, plus an INIT_EVENT, as he did it.

float3 - question
we came across an other issue: as float3 is a class (and inherits x,y,z from SFloat3): is it possible to send it through the C interface or are there compatibility issues? if not, should we send a float[3] or have a simple C struct with x,y,z?

NullEvent - solved
I still think it would not have to be in the C interface, but as it causes no harm, and zenzike thinks it will be useful, i would leave it there (with an explanation in the source code, why we have it, so others wont get confused, as did AF and me). The reason for having it: in the far future, when spring possibly uses Events internally, it could be useful, and 0 fits well for the NullEvent, so we include it now already, to not have to break compatibility in the future.

Callback - question
the current implementation of the C interface uses the old C++ AICallback class; a pointer to it is passed through the C interface. i think we would need to have the whole callback in C, but as we were not sure, we wanted to ask.
Tobi
Spring Developer
Posts: 4598
Joined: 01 Jun 2005, 11:36

Re: Interface Redesign

Post by Tobi »

hoijui wrote: float3 - question
we came across an other issue: as float3 is a class (and inherits x,y,z from SFloat3): is it possible to send it through the C interface or are there compatibility issues? if not, should we send a float[3] or have a simple C struct with x,y,z?
A struct/array would be safest I think. When passing pointer to it you could just cast the float3 to this struct/array and it would work. Or if you are just using it for passing positions etc. then I don't think it hurts at all to just put separate position_x, position_y, position_z members in the event args. Then C++ layer can convert it to AI side vector (float3) class.
Callback - question
the current implementation of the C interface uses the old C++ AICallback class; a pointer to it is passed through the C interface. i think we would need to have the whole callback in C, but as we were not sure, we wanted to ask.
Yes, you'd need entire interface to be C. If you pass around and use pointers to C++ stuff you still have ABI problems and can only use AIs compiled with same compiler as Spring.
User avatar
AF
AI Developer
Posts: 20687
Joined: 14 Sep 2004, 11:32

Re: Interface Redesign

Post by AF »

or you could use float* and then pass the float directly into a float3 constructor on the AI end.
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Interface Redesign

Post by hoijui »

thank you!

so we have 3 options:
1. separate position_x, position_y, position_z members in the event, like this:

Code: Select all

struct SAbcEvent {
	int unitId;
	boolean xyz;
	float position_x;
	float position_y;
	float position_z;
}
2. float[3]/float*
3. a dedicated struct just for the interface, like this:

Code: Select all

struct AIFloat3 {
	float x;
	float y;
	float z;
}
We can cast 2 and 3 to and from float3 (because all C++ compilers save class member variables in the same way/order?). I see pros on all 3 variants, but if we really can just cast a float3 into an AIFloat3, then i tend to solution 3. One could still cast it into a float* on the AI side again (if i got tobi right), if it was easier to use in a specific language.
User avatar
AF
AI Developer
Posts: 20687
Joined: 14 Sep 2004, 11:32

Re: Interface Redesign

Post by AF »

I thought structs inside a struct weren't a good idea for a cross-compiler C API?
zenzike
Posts: 77
Joined: 12 Apr 2008, 13:19

Re: Interface Redesign

Post by zenzike »

I asked about the structs in structs problem in another post, I think tobi said there wasn't a problem.

I'm pretty sure we can pass the SFloat3 around, since it is (essentially) a struct; it has inline constructors, all its properties are public and it has no member functions (As a footnote, I've created another thread about renaming the float3 class to something more sensible.)
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Interface Redesign

Post by hoijui »

i just know that nested structs are a problem (at least with SWIG), but this should not be (surely is not for SWIG).

i think we should not pass the float3 through the C interface as such, uncasted.
Tobi
Spring Developer
Posts: 4598
Joined: 01 Jun 2005, 11:36

Re: Interface Redesign

Post by Tobi »

AF wrote:I thought structs inside a struct weren't a good idea for a cross-compiler C API?
Tobi wrote: A struct/array would be safest I think. When passing pointer to it you could just cast the float3 to this struct/array and it would work.
[...]
Then C++ layer can convert it to AI side vector (float3) class.
hoiju wrote: We can cast 2 and 3 to and from float3 (because all C++ compilers save class member variables in the same way/order?). I see pros on all 3 variants, but if we really can just cast a float3 into an AIFloat3, then i tend to solution 3. One could still cast it into a float* on the AI side again (if i got tobi right), if it was easier to use in a specific language.
Yes, in memory layout the following are indentical on all compilers/platforms I know, and hence you can just use dirty reinterpret_casts to get to the data one way or another, it really doesn't matter much TBH as long as you keep the "standard" x,y,z order everywhere.

Code: Select all

struct {
  float x, y, z;
};

Code: Select all

float xyz[3];

Code: Select all

float x, y, z;
zenzike
Posts: 77
Joined: 12 Apr 2008, 13:19

Re: Interface Redesign

Post by zenzike »

For the record, here's a pretty good article [1] that explains some of the details we're dealing with. I'll be heading for this kind of C/C++ interaction as soon as people confirm that they're happy with the way handleEvent works.

In particular, I'll make the AIEvents subclass the AISEvents structs, since it makes the whole thing simpler.

[1] http://developers.sun.com/solaris/articles/mixing.html
User avatar
hoijui
Former Engine Dev
Posts: 4344
Joined: 22 Sep 2007, 09:51

Re: Interface Redesign

Post by hoijui »

Tobi wrote: in memory layout the following are indentical on all compilers/platforms I know, and hence you can just use dirty reinterpret_casts to get to the data one way or another, it really doesn't matter much TBH as long as you keep the "standard" x,y,z order everywhere.

Code: Select all

struct {
  float x, y, z;
};

Code: Select all

float xyz[3];

Code: Select all

float x, y, z;
what about this?:

Code: Select all

class XZY {
  float x, y, z;
  void foo();
};
thanks for the link zenzike. what i read there, which could be used as solution here, seem kind of hacky :D
User avatar
AF
AI Developer
Posts: 20687
Joined: 14 Sep 2004, 11:32

Re: Interface Redesign

Post by AF »

What if we wish to write an AI in pure C? Best to use structs when possible rather than classes.
zenzike
Posts: 77
Joined: 12 Apr 2008, 13:19

Re: Interface Redesign

Post by zenzike »

hoijui wrote: what about this?:

Code: Select all

class XZY {
  float x, y, z;
  void foo();
};
That won't work since there's a member function. We couldn't reliably have an array of these objects.
Post Reply

Return to “AI”