DecouplingWorldForge

From WorldForgeWiki
Jump to: navigation, search

Description

This is a list of notes while creating a WorldCompiler, and determining how decoupling (and therefore a compiler) should work.


Links

This started with an effort to decouple the server (DecouplingCyphesis), then the client (DecouplingEmber). The motivation was a set of DecouplingUseCases. Basically, the ability for world authors to quickly and easily build server + client packages for distribution. The main outcome is a WorldCompiler.


Notes

Open Questions

As noted in DecouplingUseCases, I think cyphesis and ember are powerful engines that can be used to build multiplayer games (duh). But to really call them engines, you have to prove they can be pulled out of their current configuration trees, and placed into new configurations where they still work. For several weeks I spent time seeing how that would work for both.

At this point, I've managed to get both decoupled. There are a few features that are not enabled yet (such as WFUT) but those can follow later. Nothing in the decoupling effort blocks those features, they are simply additional complexity I didn't want to address yet. World authors can start building rich games without them.

Decoupling was definitely a learning exercise, and the end result wasn't 100% what I was expecting. There were a lot of small issues and idiosyncracies to work through, but on the whole I found I could get everything to work. The main question now is: should I add additional transformation layers, or use the existing formats without modification?

For instance, should I compile the ruleset xml file? Here is what a common entry looks like:

 <map>
   <map name="attributes">
     <map name="bbox">
       <list name="default">
         <float>-0.6</float>
         <float>-0.26</float>
         <float>0</float>
         <float>1.3</float>
         <float>0.26</float>
         <float>1</float>
       </list>
       <string name="visibility">public</string>
     </map>
     <map name="mass">
       <float name="default">20</float>
       <string name="visibility">public</string>
     </map>
     <map name="maxmass">
       <float name="default">30</float>
       <string name="visibility">private</string>
     </map>
   </map>
   <string name="id">wolf</string>
   <map name="mind">
     <string name="language">python</string>
     <string name="name">mind.WolfMind</string>
   </map>
   <string name="objtype">class</string>
   <list name="parents">
     <string>character</string>
   </list>
 </map>

For comparison, here is the format my world compiler currently uses:

#
# class file
#

id              wolf

bounding-box {
       x0      -0.6
       y0      -0.26
       z0      0
       x1      1.3
       y1      0.26
       z1      1
}


mass            20

maxmass         30

mind            mind.animals.WolfMind

parent          character

modeldef        wolf

I think the world compiler format is far simpler, more straightforward, and easier to work with! It handles the bounding box better, and it lets the mind describe itself (language specification) rather than having the author worry about that. And, even better, the world compiler format specifies how the class should be rendered (modeldef).

But it wasn't as clear for the modelmapping or modeldefinition layers. Here is a sample model definition:

      <model name="wolf" usescaleof="height" showcontained="false" >
               <submodels>
                       <submodel mesh="3d_objects/creatures/quadruped_v2/models/wolf/normal_wolf/normal_wolf.mesh" >
                       <parts>
                               <part name="main" show="true" />
                       </parts>
                       </submodel>
               </submodels>
               <actions>
                       <action name="__movement_walk">
                               <animations>
                                       <animation iterations="1">
                                               <animationpart name="walk" weight="1.0" />
                                       </animation>
                               </animations>
                       </action>
                       <action name="__movement_idle">
                               <animations>
                                       <animation iterations="1">
                                               <animationpart name="idle" weight="1.0" />
                                       </animation>
                               </animations>
                       </action>
               </actions>
       </model>

And here is the world compiler version:

#
# wolf.modeldef file
#
# Describes how a wolf should be rendered (what 3D mesh to use,
# animations, etc.)
#

usescaleof      height

submodel {
       model   animals.wolf            # uses the wolf model

       part {
               name    main
               show    true
       }
}


action {
       name    __movement_idle

       animation {
               name    idle
       }
}

Here the world compiler version is a little more succinct, but it is a 1-to-1 mapping. Here I really started worrying that attempting to transform from a custom format to the ember format would result in an unnecessary layer, and every new feature introduced in ember would require (and be blocked by) work in the compiler.

For other objects, such as meshes, materials, skeletons, and images, there was no attempt to use a canonical format, because it was obviously not worthwhile. This was especially painful for meshes, because they contained explicit path information that I wanted the compiler to handle. But adding transformations for all of the resource formats would be very time-consuming and pretty much impossible to maintain.

So at the moment I've got a hybrid model: some objects (ruleset entries, model definitions and maps) are generated from a canonical format, whereas other objects are just kept in their native format. Should I just switch to native formats everywhere?

There is one advantage to canonical formats: if I wanted to introduce a new target (for instance, the Sear client) I could just add a transform for that. But I worry that each client may have such custom rendering behavior that a transform layer would be harder to write and maintain then having content authors provide native formats for each client.

Also, as I've been playing with the compiler, the transformation layers aren't the main point. At this point, I think the biggest value-add of a WorldCompiler is the ability for content and world authors to exchange atomic units of content (such as models, behaviors, classes, etc). The compiler will handle all of the merges and configuration glue, so authors can really focus on building. Transformations don't make much difference for that.


Resolution

For now, I've reverted back to native file types. I can extract some necessary metadata using XML::XPath libraries, so the files aren't entirely opaque. And this means the compiler will never get in the way of ember/cyphesis features (or other binaries in the future).

Here is the current content tree, that is, the input to the world compiler. Working client/servier packages are generated from this:

content
|-- behaviors
|   |-- animals
|   `-- core
|-- bin
|   |-- client
|   |   |-- lib
|   |   |   `-- OGRE
|   |   `-- media
|   |       |-- shared
|   |       |   |-- gui
|   |       |   |-- packs
|   |       |   `-- textures
|   |       `-- user
|   |           `-- packs
|   `-- server
|-- etc
|   `-- client
|-- modeldefs
|-- modelmaps
|-- models
|   |-- male
|   |-- sky
|   |-- water
|   `-- wolf
|-- objtypes
`-- world

The directory structure is mostly arbitrary, since the compiler will recursively crawl as necessary. But it illustrates some of the design.

  • Package binaries are just copied over flat
  • All server etc files are autogenerated (the client still has some opaque etc files)
  • Behaviors (scripts), model definitions, model maps, models, and ruleset object types are broken out into atomic units so they can be independenty shared.
  • The world (placement of objects etc) is still pretty basic. This will expand as I'm able to stop worrying about decoupling the binaries and focus on world construction.

I suspect most authors and content creators will deal with larger atomic units, like an entire "wolf" package that includes AI, 3d models, modeldef and modelmaps, ruleset specification, etc. So I'll probably tweak the directory structure a bit in an attempt to mimic the expected use.


First Use Case: Deathmatch

I wanted to pick a simple game to test the world compiler. Some sort of deathmatch game seems best. I'm not a huge fan of deathmatch games, but they have the huge virtue of being pretty simple. There doesn't need to be an elaborate skill system etc. It is just simple event handling on the client and server.

So what would this involve?

  1. Construct a simple world for the deathmatch. This is straightforward to do now.
  2. Set up a primitive combat system. This has subcomponents:
    1. Need character actions like "attack" or "throw" or "shoot" or "taunt" that get relayed to the server.
    2. Server needs a framework to receive and dispatch events to registered handlers
    3. Handlers need to create new objects on the fly (flying rocks, arrows, etc.)
    4. When projectiles hit something, the server needs to determine the consequences (disappears? damage?)
  3. If a character dies, they should respawn somewhere (possibly after a short wait period)

Nothing very difficult, but I don't know how to implement steps 2+ right now.



Media Packages

As I said in a previous entry, I think content authors will construct entities as atomic units. For instance, they'll create the 3d model and animiation, and add behaviors and ruleset specifications for the server.

So I reworked the content tree (the source used by the compiler to generate client and server configurations) and now it looks like this:

content
|-- behaviors
|   `-- core
|-- bin
|   |-- client
|   |   |-- lib
|   |   |   `-- OGRE
|   |   `-- media
|   |       |-- shared
|   |       |   |-- gui
|   |       |   |   `-- icons
|   |       |   |-- packs
|   |       |   `-- textures
|   |       `-- user
|   |           `-- packs
|   `-- server
|-- etc
|   `-- client
|-- modeldefs
|-- modelmaps
|-- models
|   |-- sky
|   `-- water
|-- objtypes
|-- packages
|   |-- male
|   `-- wolf
`-- world

The change from previously is the addition of the "packages" directory, which contains the male and wolf subdirectories. The wolf directory looks like this:

wolf/
|-- WolfMind.behavior
|-- WolfMind.py
|-- greyhair.material
|-- greyhair.png
|-- normal_wolf.mesh
|-- normal_wolf.skeleton
|-- wolf.model
|-- wolf.modeldef.xml
|-- wolf.modelmap.xml
`-- wolf.objtype.xml

The mesh and skeleton files are the native OGRE files. The greyhair.material file is the OGRE material as referenced by the mesh, and the greyhair.png file is the texture used. The wolf.model file is a compiler-specific file which lists how the model should be copied into the final config tree. The xml files are the modeldefinition, modelmap entry, and ruleset object entry. The WolfMind.py file is the wolf's AI, and the WolfMind.behavior file is a compiler-specific file which tells the compiler where the behavior goes in the compiled output.

The entire directory could be packaged (tar'd/zip'd) and shared with other game authors. I'm calling that a "media package" for now, but it needs a better name (since it contains entity behaviors and specifications that don't qualify as media).