For a long time I’ve wanted to do a write-up on the ArchType based model. It’s as old as dirt and shamefully simple. But simple is good. It scales well to different complexity needs. For example it works really well as a data reduction technique and can be appropriate for low complexity system that otherwise wouldn’t need anything beyond the native abstraction model. It’s a top level model that can work hand-in-hand with other top level models, such as Actor based. And even though my motivation is in an attempt to show something one can do instead of the component-based model…the two would actually work “well” together assuming you think component-based has some merit.
Now I’m going to attempt to talk at multiple levels of background knowledge. Plough ahead if something sails over your head.
NOTE: That all of my examples will be super basic and poorly designed…this is going to be long enough as it is. Also I’m going to break this up into parts and any only build on this (into a usable form) if there seems to be interest.
ArchType in overview
The ArchType model has many different names and is probably most frequently used without the programmer calling it anything. At its most basic it looks like the following:
public class ArchType
{
// whatever
}
public class Entity
{
ArchType archType;
// whatever
}
That’s it. So simple that it will probably seem worthless if you’ve never used the model before.
The major role of the Entity
class is to store the state-data specific of a given instance of an entity. The major role of the ArchType
class is to store the data that “defines” what the entity actually is and does. Enough for now.
Java’s abstraction model, briefly, inexactly and with much hand waving
Java’s base abstraction model is class based object oriented. This basically means that the abstraction model manages two memory chucks. One for the class and another for an instance of that class. The instance memory chuck only stores a pointer to its class’s memory chunk (along with some bookkeeping info) and the remainder is all the instance variables (fields) of that class along with the instance variables that it inherited from its parent.
It is also “closed class” based, which means that all of it’s members (static & instance) are defined at compile time and none may be added or removed at runtime. (Yes, there are minor ways around some of this) Additionally methods are immutable…you cannot set a method to another compatible method at runtime.
To simplify the above, basically an instance only stores the state-data needed per instance (along with some bookkeeping). The remainder of the storage is placed with the class since all of this data is common among all instances of a given class.
public class A
{
public static int sa;
public int ia;
public static void hello() { ... }
public void world(...) { ... }
}
public class B extends A
{
public static int sb;
public int ib;
public static void hey() { ... }
public void you(...) { ... }
}
gets logically translated into the following memory chunks (in pairs):
class$A {
class* parent; // Object's class
method* sMethods; // table of static methods
method* iMethods; // table of instance methods
}
instance$A {
word bookeeping1; // hotspot internal magic
word bookeeping2; // hotspot internal magic
class* class; // class$A
int ia; // instances fields of 'A'
}
class$B {
class* parent; // A's class
method* sMethods; // table of static methods
method* iMethods; // table of instance methods
}
instance$B {
word bookeeping1; // hotspot internal magic
word bookeeping2; // hotspot internal magic
class* class; // class$B
int ia; // instances fields of 'A'
int ib; // instances fields of 'B'
}
So when an instance method is called, such as: b.world(...)
. This gets translated into b.class.iMethods[slot of world](b, ...)
Compare this with the basic outline of ArchType above and you’ll see alot of things in common. An Entity has the same major role as an instance and an ArchType has the same major role as a class.
CODE IS DATA
In data driven design you avoid creating a new type (class) if two things only vary by data.
public abstract class SillyPotionClass extends SillyItemClass
{
public abstract void onDrink(Entity drinker);
}
public class NoobHealingPotion extends SillyPotionClass
{
public void onDrink(Entity drinker) { drinker.heal(1); }
}
public class SuperDuperHealingPotion extends SillyPotionClass
{
public void onDrink(Entity drinker) { drinker.heal(50); }
}
// I refactor into:
public class HealingPotion extends SillyPotionClass
{
private int healAmount;
public void onDrink(Entity drinker) { drinker.heal(healAmount); }
}
But wait! CODE IS DATA. All potions work in the same way. You use them and they do something. But wait! Scrolls do the exactly same. Sure the animations, descriptions, models, etc. etc. are all different…but that’s just data…and so is the handling code.
A poorly designed example:
public class OnUse extends ....
{
public static OnUse SINK = new OnUse();
public void OnUse(Entity user, Entity item)
{
// default to a script error log
Script.errorlog("Yo: " + user + " somehow triggered on OnUse on " + item + " and it ain't set-up...fix yo bug scripter!");
}
}
public class OnUseEffect extends OnUse
{
final Effect effect;
final int value;
public OnUseEffect(Effect effect, int amount)
{
this.effect = effect;
this.value = value;
}
public void OnUse(Entity user, Entity item)
{
user.apply(effect, value);
}
}
public class SillySingleUseItem extends SillyItemClass
{
OnUse onUse;
}
In fact the vast majority of Entities really behave in the exactly same way. The player or the computer causes some event to occur and the underlying code determines what the effect of that event causes on the world. In the above event handler example, constant data about the effect is moved into the event handling code. And note that the reference to the handler doesn’t need to be in the ‘entity’ at all. All idential items will (generally) have identical sets of event handlers…move it into the ArchType…along with all other common data.
That’s enough for now.