"Anonymous" Type Identity?

Blitz3D Forums/Blitz3D Programming/"Anonymous" Type Identity?

ClayPigeon(Posted 2012) [#1]
Is it possible to somehow create several different types, then create a function that can operate on all of them? For example, say I have an "enemy" type and a "player" type. If I create a "move" function to relocate them, I have to make a separate one to change the coordinates of one type and the other type, even though they both contain the same variables. So, my question is how do I get/set variables of different types without writing a separate code for each? (I'm thinking it might involve Object/Handle)

Last edited 2012


Yasha(Posted 2012) [#2]
A function can accept objects of different type by passing the Int Handle: any type can be cast to Int in this way. But each Int can only be cast back to an object of the original type. You can create a single "polymorphic interface" function, that accepts a handle - but it has to delegate to implementation code written for each of the types it can accept. In other words, no, there is no easy way to have the same code work on values of different types (without using unsafe DLL hacks), because types force the separation of code.


What you can do, however, is make any data that you want to handle in one specific way only ever exist in one type - and you can then have "addon" types that extend the data with case-specific extra stuff. That way, objects of different absolute type both "extend" the same base class, and can share the code that operates on this base. To give an example for your situation:

Type Actor    ;An actor is any (N)PC with movement
    Field x#, y#, z#
    Field this    ;Handle of the "rest" of the object
End Type

Type EnemyActor
    Field base.Actor    ;Reference to the shared base type
    Field enemyStuff
End Type

Type PlayerActor
    Field base.Actor
    Field playerStuff
End Type

Function NewEnemy.EnemyActor(x#, y#, z#, enemyStuff)
    Local e.EnemyActor = New EnemyActor
    e\base = private_NewActor_(x, y, z, Handle e)
    e\enemyStuff = enemyStuff
    Return e
End Function

Function NewPlayer.PlayerActor(x#, y#, z#, playerStuff)
    Local p.PlayerActor = New PlayerActor
    p\base = private_NewActor_(x, y, z, Handle p)
    p\playerStuff = enemyStuff
    Return p
End Function

;Private constructor because we don't want people actually creating these separately
Function private_NewActor_.Actor(x#, y#, z#, this)
    Local a.Actor = New Actor
    a\x = x : a\y = y : a\z = z
    a\this = this
    Return a
End Function


; Assume the existence of FreeEnemy, FreePlayer, private_FreeActor_ here (obvious implementations)


Function MoveActor(a.Actor, x#, y#, z#)
    a\x = a\x + x : a\y = a\y + y : a\z = a\z + z
End Function


;Instances that are strongly typed, separate
Local e.EnemyActor = NewEnemy(1.2, 3.4, 5.6)
Local p.PlayerActor = NewPlayer(7.8, 9.1, 0.1)

;Generic collection
Local actors.Actor[1]
actors[0] = e\base     ;To cast to the shared supertype, simply get the \base member
actors[1] = p\base

Local i : For i = 0 to 1
    MoveActor actors[i], 10, 0, 0    ;Move players and enemies alike
Next

Local pInvalid.PlayerActor = Object.PlayerActor actors[0]\this    ;Null, because this Actor is not a Player

Local pValid.PlayerActor = Object.PlayerActor actors[1]\this    ;Not Null - valid Player


You can see an example of this in real-world code in my extended version of the bOGL OpenGL 3D engine project, where it's used to share the Entity class between Camera, Mesh, Light and Pivot (it uses a slightly faster system than Handle, but I'm sure you'll see the relevant differences).

...if you find yourself relying on this sort of thing heavily though, what you really want to do is move up to a language that properly supports object-orientation, like BlitzMax. What we've got above is basically a hand-implemented version of "inheritance", one of the Four Pillars of OOP. Language-level support is handy for this.


You could also think of this the opposite way around: instead of Enemy and Player being subtypes of Actor, you could consider them independent types that both "have" a Movable component object. So Movement wouldn't affect the whole object, but only one property of the object (this is much more dynamic, in that it separates the application of effects from any notion of a "class hierarchy", but it also requires you to explicitly design objects as "owning" components, and to remember to examine those components separately).

Last edited 2012


ClayPigeon(Posted 2012) [#3]
Wow, that was a very complete answer! Thank you for taking the effort to explain that! It makes perfect sense to me, and I don't know why I didn't think of it, or why I didn't realize I was trying to emulate inheritance. Thanks again!


Kryzon(Posted 2012) [#4]
I agree, that was a very interesting use of Object and Handle.
I hadn't thought of that; thanks for sharing.