programming practice: composition (aggregation)?

BlitzMax Forums/BlitzMax Programming/programming practice: composition (aggregation)?

gameproducer(Posted 2008) [#1]
AlexO pointed out composition(aggregation) over inheritance in this thread, and I'd like to know more about how to use it in bmax (syntax+examples)

Inheritance is something I know how to use, and I've just recently heard about composition (or aggregation). Could somebody provide a sample bmax code that uses composition - since right now I'm very new to bmax+OO including composition) stuff in general... I've read & seen pictures about what this is, but wasn't sure how it works in bmax.

I know that my game would seriously benefit from having a composition (since in Blitz3D I had this one HUGE "class" that had EVERYTHING in it :D). Now I'd like to understand how compose things in bmax.

Any pointers/links/aid would be great.


AlexO(Posted 2008) [#2]
Hi Again,

I figured I'd post a little something about it since I've been using it for a bit. I wrote up a quickie (although a bit lengthy) example of how it could work in blitzmax.

Let's start with the syntax of how it'd be used since ultimately that's what we're trying to go for once all the nitty-gritty is implemented:

so let's say your game requires physics for multiple objects. But you have one particular type of 'object' that needs physics AND be controlled by the player BUT you don't want collision detection for the player as they can walk through walls for instance:

' lets make an object that has physics, AND can be controlled by our player.
Local myPlayer:TBaseObject = TBaseObject.Create("Robot", 40, 40)

'...now to give our new empty object physics and controls we simply add the functionality to them
myPlayer.AddComponent(New TPhysicsComponent)
myPlayer.AddComponent(New TControllerComponent)
myPlayer.Init()

' lets run it!
myPlayer.Update()

Following that logic, say your player can shoot different types of bullets. Bullets collide with objects but don't obey your normal physics. Let's say one of these types of bullets is a laser. So the setup code could look something like this, note that I don't add a physics component because my bullets don't need them:

' now lets make a bullet with laser flight
Local myBullet:TBaseObject = TBaseObject.Create("Laser", 30, 30)
myBullet.AddComponent(New TLaserComponent)
myBullet.AddComponent(New TCollisionComponent)
myBullet.Init()

myBullet.Update()
' and so on...

By now hopefully things make a little more sense as to how different object 'types' can be composed, and potentially how much flexibility you now have when designing your in-game objects. Now when I want to tweak how my laser logic works I just edit the laser component, with very little worry about affecting other portions of the 'whole'.

Ok, an example implementation follows. I apologize for the length, but it's a complete working example so for those that wish to know the basics of how it works hopefully can benefit from this.

Let's start with our base object that will be housing these components:

rem
this is our 'container' class.
it provides very basic functionality that we 
determine every 'object' should have.
in this example I've only determined
that each 'object type' should have a name and position
end rem
Type TBaseObject
	Field _name:String
	Field _components:TMap = CreateMap()
	Field _x:Float
	Field _y:Float
	
	' factory method to help create a base object.
	Function Create:TBaseObject(name:String, x:Float, y:Float)
		Local obj:TBaseObject = New TBaseObject
		obj._name = name
		obj._x = x
		obj._y = y
		Return obj
	End Function
	
	' some basic setters/getters
	Method SetPosition(x:Float, y:Float)
		_x = x
		_y = y
	End Method
	
	Method GetX:Float()
		Return _x
	End Method
	
	Method GetY:Float()
		Return _y
	End Method
	
	Method GetName:String()
		Return _name
	End Method
	
	' to provide some coherency
	' we'll add an init() function
	' that we call once all the components
	' for an object have been added
	' and wish to initialize themselves
	Method Init()
		For Local comp:TComponent = EachIn _components.Values()
			comp._OnAdd(Self)
		Next
	End Method
	
	' our update function becomes
	' simplified for this object
	' since all the functionality
	' is tucked away in it's components.
	Method Update()
		For Local comp:TComponent = EachIn _components.Values()
			comp.Update()
		Next
	End Method
	
	' this allows use to search for a particular component
	' and functionality based on name. Since it is 
	' using a tmap, retrevial is fast compared to a linked list.
	' the only downside is 'update' order is not easily determined. a trade-off
	' but could be solved with a more complex solution.
	Method GetComponent:TComponent(componentName:String)
		If _components.Contains(componentName) Then
			Return TComponent(_components.ValueForKey(componentName))
		End If
		Return Null
	End Method
	
	' this adds a new type of component
	' to this object. In this implementation
	'you cannot add, for instance, 2 physics components, as it doesn't make much sense
	Method AddComponent(component:TComponent)
		If Not _components.Contains(component.GetType()) Then
			_components.Insert(component.GetType(), component)
		End If
	End Method
End Type

Nothing too mind-blowing out of the above. I chose to use a TMap as my collection class for components for faster 'look ups'. Others may choose to stick with lists, but that detail is irrelevant for this example. One constraint about the above system: you cannot add the same type of component more than once to a single base object.

Now for our component class:

rem
our base component class.
each component class _must_ have a way of identifying what type 
of component it is. For this example we will just use a 'type' string. 
This isn't the most 'strongly' typed way to do it, but for simplicity
it will do.
end rem
Type TComponent Abstract
	Field _owner:TBaseObject
	Field _type:String
	
	Method New ()
		_type = "TComponent"
	End Method
	
	Method GetOwner:TBaseObject()
		Return _owner
	End Method
	
	Method GetType:String()
		Return _type
	End Method
	
	Method SetType(typeName:String)
		_type = typeName
	End Method
	
	
	Method OnAdd(owner:TBaseObject) Abstract
	Method OnRemove() Abstract
	Method Update() Abstract
	
	rem
	this is consider an 'internal' method
	used only by our base component class to help facilitate the aggregation
	end rem
	Method _OnAdd(owner:TBaseObject)
		_owner = owner
		OnAdd(owner)
	End Method
	
	rem
	another internal method to help facilitate the component structure
	end rem
	Method _OnRemove()
		_owner = Null
		OnRemove()
	End Method
End Type

A rather small class. It only requires children to implement an 'Update','OnAdd', and 'OnRemove'. So it is pretty open to do essentially whatever you want. You could even require components to have a Draw() method, making components a viable way of implementing different rendering styles.

Now how is a component class implemented from the above code?

' a basic physics component could encapsulate the physics calculations
' and formulas needed to do simulations.
Type TPhysicsComponent Extends TComponent
	Field _velx:Float
	Field _vely:Float
	
	Method New ()
		SetType("TPhysicsComponent")
	End Method
	
	Method OnAdd(owner:TBaseObject)
		' sometimes a component may want to query for other components
		' in the owner to gain access to other data if there are dependencies.
	End Method
	
	Method OnRemove()
		
	End Method
	
	' a trivial example for 'physics' functionality.
	Method Update()
		Local owner:TBaseObject = Self.GetOwner()
		Local x:Float = _velx + 3
		Local y:Float = _vely + 3
		owner.SetPosition(x, y)
		Print "physics update!"
	End Method
End Type

A very rudimentary (ie non-functioning) physics component. You could imagine a physics component that did actual physics calculations would be very helpful, and actually very re-usable when implemented in this fashion.

I'm only going to paste one more implementation that addresses an issue that arises from this type of design:
' a basic controler behavior that could attached.
Type TControllerComponent Extends TComponent
	
	Field _physics:TPhysicsComponent
	
	Method New ()
		SetType("TRobotAIComponent")
	End Method
	
	Method OnAdd(owner:TBaseObject)
		'lets say for example this controller type needs to know the object's velocity to function. lets query for it:
		_physics = TPhysicsComponent(owner.GetComponent("TPhysicsComponent"))
		' now the above is not ideal if you wish to decouple your components. for more information on a solution that'll 
		' solve that look into Data ports:
		' http://www.gamasutra.com/view/feature/1779/implementing_dataports.php
		' these can be easily adapted to the component architecture where the 'baseObject' object is it's own
		' individual data port manager, and components and register/read/write to it's owner's data ports.
	End Method
	
	Method OnRemove()
		
	End Method
	
	Method Update()
		Print "controller update!"
	End Method
End Type

For those just skimming over the code:
Sometimes components (for example, a physics component and a collision component) need to communicate with each other in order to function. This is not uncommon, and when doing the 'large class' way it's easy for different sections to communicate. Components are supposed to be a way to de-couple functionality from each other and allow it to be reusable. You could easily 'query' for the dependent components (as above) if you don't care too much about coupling. But if you do then I highly recommend reading this article for a solution to this problem:

Implementing Dataports

Ok so that's a quick run down of a way to implement aggregation/components in bmax. Hope it's helpful :).

The whole compilable bmx file is here.


AlexO(Posted 2008) [#3]
Oh one more thing I wanted to mention. This design is not only great for game logic, but for the structure of the game application itself. I've applied it to my framework and it's been a treat to work with. Also worth noting, XNA Framework's design is of a similar nature when it comes to adding functionality to the game (They use components to provide functionality and services as a means to access them).

So if you have a similar generic game class you could theoretically add in game components like 'GUI', 'Networking', 'Player profile saving', etc.


gameproducer(Posted 2008) [#4]
Thanks AlexO, I'll take a look at this and comment later.

(Also spotted this: http://geekswithblogs.net/mahesh/archive/2006/07/05/84120.aspx - comes handy for those of us who are learning OO standards... I presume in bmax there's no "interfaces"?)


AlexO(Posted 2008) [#5]
sadly no interfaces in bmax :(. Not in the way that article describes it anyway (C#).


Muttley(Posted 2008) [#6]
Very interesting/useful example Alex, thanks for sharing. :)


verfum(Posted 2008) [#7]
In a simple example this is aggregation and composition in C++

c++ Composition, similar Association
struct A
{

private:
      B* b;
};

Bmax composition
Type A

      Field b:B;
End Type

c++ Aggregation
struct Ship
{

private:
       std::list<Weapon*> m_weapons;
};

Bmax Aggregation
Type Ship

      Field m_weapons:TList;
End Type

I'm pretty sure that's right.
But Bamx lists are not as powerfull as C lists, and vectors.


Warpy(Posted 2008) [#8]
In what way are bmax lists not as powerful as C lists? I'd be interested in adding some functionality to make them more useful.


verfum(Posted 2008) [#9]
This might be one of them, I did go over it with a friend who has been programming in C for about 16 years, I've only been using it for about 4 months, I think this is what we decided.

In Blitz you define a list like this
Local myList:Tlist

That creates a general list object, for anything, there is no targeted scope, or type, it just seems vague to what exactly that list is holding, whereas in C you do this.
std::vector<Entity*> entity_list;

or

std::vector<Enemy*> enemy_list;


That way you are creating a list object with a defined type, this is more logical for object oriented programming, isn't it? So say you have a list that you want to store a bunch of Weapons, or Items, in Bmax they would be defined like
Field m_weapons:TList

At the end of the day, that is a list called m_weapons, anything could be added to that, it's just simply a list, of somethings that can be added. I just don't see how it's logical to OO programming, I might be wrong, it was a while ago when we were looking into it.


tonyg(Posted 2008) [#10]
That's not exactly an indication of 'power' though.
I really like Bmax implementation of lists and if you're REALLY going down the OO route you would be hiding list names and only accessing them via the objects methods.... wouldn't you?
Or is this all over-analysis for, what appears to me, an undefined problem?


verfum(Posted 2008) [#11]
Ah, your right, you do Local p:Player = EachIn entity_list don't you, it's a bit more long winded than that in C++, you have to do type_casting, I'll have a re-think and see what it was that we were talking about on this subject.


Arowx(Posted 2008) [#12]
Isn't that just a typesafe issue in effect only objects of a specific type can be added to a c++ STL list.

Note this is not a default C++ language feature but the vector template in the Standard Template Library (a very nice addon to the C++ langugae).

You can have a nice messy pointer based list in C++ as well!

As blitzmax has reflection it would be possible to create a typesafe wrapper for a List that prevent's objects being added which do not belong to a specific type but this would have an overhead when adding items to the list!

In effect though the C++ compiler will complain when you attempt to add objects of the wrong type whereas in BlitzMax you would only recieve an error at runtime!


verfum(Posted 2008) [#13]
Thinking about it further and having a quick chat with him he said was it something to do with the lack of making lists private? Something to do with other Types having the ability to access the lists and adding themselves, but that's again a design thing, not directly to do with lists.


gameproducer(Posted 2008) [#14]
Thanks guys.

I've been going through this... and the example 'composition' for some reason doesn't seem similar to the composition examples the book I'm reading has (Head First: Design Patterns) - maybe it's just me :)

I'll read the book chapter couple of more times and compare with this thread...

I wonder what's the main difference between an interface and an abstract class is - can they be exactly the same too??


z80jim(Posted 2008) [#15]
Thanks AlexO.

I loved your tutorial and plan to make use of it. I thought it was interesting because it is a different take on an approach I have been doing anyway.

In my Ultima IV type RPG I might do something like this:



So I would have these other objects ( your compnents ), or pointers to them, defined in my base object in case that particular object needed that functionality. So my Chest entity would have a pointer to TContainer but would not have one to TMove or TVehicle etc.

What I didn't like is I would end up with a long list of these pointers in my base object and everytime I think of a new one I would have to modify my base class. So your approach functionally ends up the same but I think is much more elegant and probably flexible. I don't have to explicitly code fields for each possible component into my base class.

If you are inclined to explain other similar types of design patterns and how they are used in BM I would be a big fan of that. I think this little tutorial should go in the Tutorial thread.