1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
|
Brief description of architecture
=================================
by Hugh Perkins
This may or may not be moved to wiki in the future, but for now it is here. It's
easy for me to update without needing internet access and so on ,
and it's automatically versioned with the sources.
Origins of the code
===================
This code was originally CSAI ("C# AI), which worked quite well, beat all working
AIs at the time ;-)
This was partially refactored and redesigned into CSAI2, which was never completed
following the migration of spring from using Microsoft Visual C++ as its
Windows compiler, to using mingw.
CSAI was not portable to linux, although I did spend a bit of time getting
a mono loader working, but it wasn't really as satisfactory as the .Net one,
and so it fell into an unmaintained state.
With hoijui's Java interface, hopefully there is now an opportunity to have AIs
written in a C#-like language, which are portable across platforms, and
won't break constantly every time the spring engine is upgraded...
The codebase of HughAI is essentially CSAI2, ported to Java. As CSAI2 was
never completed, HughAI initially is in a bit of a half-way state ;-) and
not quite ready for usage yet, but hopefully it can be fun for a developer
to tweak and play with, and might provide some ideas, and maybe even some
useful code!
Loader architecture
===================
AIInfo.lua points to SkirmishAI.jar, which is built from src-loader directory.
This is a fairly light-weight layer which is responsibility for two quite simple
things:
- load UnderlyingAI.jar, which is built from src-ai directory
- pass on AI events from the engine onto UnderlyingAI.jar's AI object
Of these, loading the underlyingAI.jar is probably the most in need of explanation.
Why do we have two-stage loading?
Simply: it means we can dynamically reload the AI whilst the game is in progress.
Couldn't we just use /kill /controlai? Possibly. I've used this way of doing it,
but that might work too.
One advantage of doing it like this is we can provide inter-load storage,
which is provided by TransLoadStorage.
Simply, TransLoadStorage lets us store primitives and native Java types, as
labelled "attributes", and these will be available, persistent, after reloading
the AI.
What/why would we reload the AI?
--------------------------------
So, we're developing the AI, and it takes ages for the game to restart each time,
and also, that way we would lose the entire current game-state, which might
be necessary in its precise current state in order to reproduce a bug we just
noticed, or an exception.
So, we tweak our AI, rebuild it, recreate UnderlyingAI.jar, and reload it. Ploof!
the new code is now running in Spring's AI engine, magically.
How does the loader work?
-------------------------
Classes cannot be unloaded individually in Java, but their classloader can be,
and along with it, all the classes unload too.
We create our own classloader object, and use that to load the UnderlyingAI.jar
classfile.
It is important that the UnderlyingAI's classes are not available in the standard
classpath, otherwise, they will be loaded by the system classloader, which is
not possible to unload, without shutting down the entire jvm runtime.
How do I use the loader?
------------------------
To do the reload: simply click "Reload AI" on the Swing GUI.
If the AI crashed during loading, and the button is not visible, in the spring
game screen, enter chat and say ".hughai reload", which will reload all hughais
in the game. (The button will only load that particular AI, no any other
hughais in the game).
How do I customize the loader?
------------------------------
The path to UnderlyingAI.jar, and the name of the underlying AI class to load
are given in the top of HughAILoader.java:
final String underlyingJarFileName = "UnderlyingAI.jar";
final String underlyingClassNameToLoad = "hughai.CSAI";
final String reloadCommandString = ".hughai reload";
You can tweak these as you wish.
You'll need to restart spring to take these tweaks into account, but at that
point you can freely reload the UnderlyingAI.jar itself.
Core highest-level class
========================
hughai.CSAI
I never changed it's name from the original C# AI ;-) It is the highest level
anyway.
It is loaded by hughai.loader.HughAILoader , and stays around until HughAILoader
loads a new AI, on request.
CSAI makes available a GameAdapter set of events, which can be subscribed to,
see later.
PlayerObjects
=============
For various reasons, I decided to try to avoid any static objects.
So, everything that used to be a Singleton in CSAI/CSAI2, is now an instance
variable in an object called "playerObjects". PlayerObjects is created by
the core CSAI object, and passed on in the constructor of pretty much
any other object.
Whether this is a good way of doing this is debatable. It kind of works ok,
but it's annoying to have to add it into every constructor...
PlayerObjects has a series of "get" commands for each object instance that
it holds, eg getMaps, getUnitController, and so on.
Each of these objects is effectively a singleton, though not actually using
the traditional singleton pattern.
PlayerObject's constructor accepts a CSAI object, and checks that it hasn't
already been constructed, by using a static list of CSAI objects for which
it has already been constructed. This ensures that each instance is unique
for instance of CSAI.
GUI
===
The GUI uses Swing. It seems that Netbeans with Matisse is an appropriate
GUI designer for this.
For now, the entire GUI is automatically generated by the registerButton
method in MainUI:
public void registerButton( String buttonText, ButtonHandler buttonHandler );
To use:
playerObjects.getMainUI().registerButton("My button text", myButtonHandler );
This will add a new button with the text "My button text" to the gui, which
will call myButtonHandler.go() whenever it is pressed.
Chat commands
==============
CSAI can be used to register chat commands. The command to register them
is:
playerObjects.getCSAI().RegisterVoiceCommand( "myvoicecommand", myhandler)
Then, when you say, in-game, ".hughai myvoicecommand", then myhandler.commandReceived
will be called.
The commandReceived method will receive additional arguments, so you can
add parameters to your commands.
AI events
=========
CSAI provides a GameAdapter registration event system, which provides the AI
events such as UnitCreated, UnitDestroyed and so on.
It can be subscribed to by doing:
playerObjects.getCSAI().registerGameListener( mygameListener );
mygamelistener will now receive the AI events. It's an adapter class, so
just override the events you're interested in.
CSAI will catch any exceptions, and print them out, so you can just let your
exceptions float up to the top.
Logging
=======
Uses java.utils.logging classes now.
UnitController, EnemyTracker Unit caching
=========================================
unit.getPos() and unit.getUnitDef() take 200 microseconds to excecute each
approximately, which is a lot, if you think about 200 units, running this
once a frame, for a total of about 1200 milliseconds per game second ;-)
So, we cache them, which doesn't prevent the 1200 milliseconds per game second
hit, but means we don't exceed this time ;-)
UnitController is responsible for caching the details of friendly units.
EnemyTracker handles enemy units.
Try to avoid calling unit.getPos() or unit.getUnitDef() directly, just go
through these objects.
Couldn't we just derive from / wrap the unit class?
---------------------------------------------------
Yes, but I felt it was marginally easier for me to do it this way for now.
If you feel you can make the architecture cleaner by creating a caching-unit
class, that could be cool....
Float3
======
The standard for positions and so on in HughAI is Float3, NOT AIFloat3.
AIFloat3 is only used at the point of interaction with the AI Java interface,
and then it is converted directly into Float3s, which provide additional
functionality, such as cross, and getDistanceSquared, and are perhaps more
lightweight than AIFloat3, which is JNA-encumbered.
You can make a Float3 from an AIFloat3 by doing:
Float3.fromAIFloat3( someaifloat3 )
and back again:
myfloat3.toAIFloat3()
Update:
- we always use a derivative of AIFloat3 or Int2 for specifying positions, or
interacting with maps, so that we can tell which type of position we are
dealing with.
- for normal unit positiosn and so on, we use a "TerrainPos"
- each map class contains it's own Int2 derivative, for example, MapHeightPos,
or BuildMapPos
DrawUtils
=========
provides functions to draw a map (from a passed in int[][] or bool[][]), draw
a unit, and draw lines.
You can also clear the whole map of previously drawn lines.
There is a bug in the java interface or in spring that limits the number of lines
you can draw without a segfault, so we limit this ourselves, to prevent the
segfault.
Clearing the map of lines resets the number of lines used back to zero.
Mapping
=======
There are a bunch of map classes in hughai.mapping. They're quite fun
to play around with, and they can be easily drawn on the screen.
losmap: the last frame number when each point on the map was seen by a friendly
unit
buildmap: each point on the map which has been built on, or has been reserved
for future usage (eg: used for metalspot reservations, to prevent building
other stuff over the metalspots)
enemymap: dynamic and static enemies
heightmap: as it says. We load it once at the beginning of the game, and cache
it for the rest. Since it takes ages to load, we cache it in the transgame
storage, so reloading the ai is really fast, after the initial load
metal: calculate and manages the metal spots on the map
movement maps: a very interesting and useful class. Divides the map into a series
of connected areas. If a unit is in area 1, and another point is also in area 1,
then it means there is a navigable route between the two points. If the areas
are different, then the areas are effectively disconnected for that unit, for
example, there may be mountains in between the two.
- where the area is 0, a unit can't go there
- there are maps for planes (always 1...), boats (areas underwater), for infantry
( can cross steep terrain), and vehicles (only gentle terrain)
slopemap: uses the heightmap to calculate the slope at each point
- used to calculate the movement maps above
Many of these map classes add buttons to the GUI to draw the map on the game
terrain.
BuildTable
==========
Caches all the unitdefs in the current mod.
WorkflowController
==================
Kind of manages everything at the moment. Whether it should is another matter...
BuildEconomy
============
Contains a list of unit to construct, with priorities, quantities, and so on.
Will probably be migrated to be read from per-mod config files.
Controllers, packcoordinators...
================================
There are several layers of controllers, running at different levels of
abstraction, which are quite hazily defined at the moment.
The top levels are currently:
Offense
Reconnaissance
The lowest levels are xxxPackCoordinator, which manage a single pack of units,
to do something quite specific, like move to a particular location, or
search the map, or attack a particular enemy.
In between are TankController and ScoutControllerRaider, which switch between
different packcoordinators as appropriate. For example, when there is no enemy
TankController switches to use a spreadsearch pack coordinator, or a guard
pack coordinator. When an enemy is sighted, it switches to an attack pack
coordinator.
packcoordinatorselector can be used to switch between different pack coordinator
instances. See tankcontroller for an example of how this works.
Interaction between controllers, and constructors
=================================================
A little up in the air. Do we want a centrally planned economy, or for the
controllers to take an interest in what is being built? Both might work, which
works better is an open question. Which is easier to maintain is an open
question.
Various List classes
====================
I don't think these are used at the moment, nor have they ever been used
probably, and they be removed in the future, so you can ignore for now:
UnitList
UnitLists
CommanderList
HelicopterLIst
TankList
Levell1ConstructorList
Level1FactoryList
...essentially, these have been migrated de facto to have unitcontroller
manage lists that come from the config file. So these classes will be removed
in the near future most likely.
Config
======
Config is read from a per-mod file in the AI's directory.
If the config file doesn't exist, it is created. As new values are added
to the config class, they are added automatically into the xml file.
The config file is read and written automatically, by using Reflection to
see the names and types of the fields in the Config class. This supports
Strings, booleans, floats, integers, and ArrayList<String>.
To add new config values, simply add an appropriate field (member variable) to the
Config class, and an appropriate getter/setter for it. Please provide an
appropriate default value, by giving it an initial value, eg:
String mynewconfigvalue = "foo";
... in this case, 'foo' is the default value for the new config value "mynewconfigvalue".
ConfigHelper contains the actual reflection code, if you need to add additional
types, or just give me a shout, since I know how it works ,and can probably add
a new type quite quickly.
Workflows
=========
Workflows.java loads workflows from xml files in the config directory
It will create an initial sample.xml file if no files are found, which
will work kind of ok in the Balanced Annihilation mod.
Tester class
============
The Tester class can be used to run various tests, such as testing some line
drawing and so on
It provides buttons on the GUI to run these tests.
|