Sign in to follow this  
Zeophlite

Sharing variables with external systems

Recommended Posts

While trying to get an external physics engine to work with my sandbox, I encountered a problem, which I can see that if I don't think about it now, I'm going to run into problems in the future. I'm programming in Java, using Slick to draw, and the physics engine is Phys2d, but I don't really think this matters - its more of an overall programming query. To that end, the code below is sorta pseudo-java. Alright, so let me try to explain my problem. On my screen, I have a list of Entity's that I want to draw (with Slick) and interact (with Phys2d). Theoretically, each Entity has an x,y location. Slick needs it to draw, and Phys2d needs it to interact. Currently, I'm just letting the x,y location be the one inside Phys2d, because I can't force Phys2d to use an internal variable from Entity.

public class Entity {
    init(world) {
        b = new Body(startx, starty, width, height);
        world.add(b);
    }
    
    draw(g) {
        g.fillRect(b.x, b.y, b.w, b.h);
    }
    
    Body b;
}



Entity e;
e.init(world);
while(running) {
    world.update()
    e.draw();
}



But, what if I want to use a different physics engine? The draw routine (which should be separate from the physics) needs to be rewritten (granted, this isn't hard with my example, but what if it was much more complex?). Also, what if I wanted to add, say, scripting to my sandbox? Whatever scripting engine I use will have its own x,y to manipulate. When I come to draw, what do I use? The only solution I could think of is as follows, and I'm wondering if there is another way I could do this? I can't be the first person who has wanted to share data between multiple external systems, right? How do you handle this situation?

interface Systems {
    update();
    static staticUpdate();
    
    preupdate(Entity e);
    postupdate(Entity e);
}

class PhysicSys implements Systems {
    PhysicSys(x,y,w,h) {
        b = new Body(x, y, w, h);
        PhysicSys.world.add(b);
    }
    
    update() {
        // do nothing, is handled elsewhere
    }
    static staticUpdate() {
        world.update();
    }
    
    preupdate(Entity e) {
        b.x = e.x;
        b.y = e.y;
    }
    postupdate(Entity e) {
        e.x = b.x;
        e.y = b.y;
    }
    
    Entity e;
    Body b;
    
    static World world;
}
class DrawSys implements Systems {
    DrawSys() {
        // do nothing
    }
    
    update() {
        g.fillRect(b.x, b.y, b.w, b.h);
    }
    static staticUpdate() {
        g.clear();
    }
    preupdate(Entity e) {
        // do nothing
    }
    postupdate(Entity e) {
        // do nothing
    }

    static Graphics g;
}
class ScriptSys implements Systems {
    ScriptSys() {
        // do nothing
    }
    
    update() {
        // do scripty stuff
    }
    static staticUpdate() {
        // do global scripty stuff
    }
    preupdate(Entity e) {
        x = e.x;
        y = e.y;
    }
    postupdate(Entity e) {
        e.x = x;
        e.y = y;
    }

    double x, y;
}


public abstract class Entity {
    abstract init();
    
    givePriorityTo(Systems s) {
        s.preupdate(e);
    }
    returnPriority() {
        s.postupdate(e);
    }
    
    list<Systems> syslist;
    private double x, y;
}

public class Boo extends Entity {
    Boo();
    
    init() {
        syslist.add(new PhysicsSys(startx,starty,w,h));
        syslist.add(new DrawSys());
        syslist.add(new ScriptSys());
    }
}



List<Systems> sysList;
List<Entity> eList;

eList.add(new Boo());

for(e : eList) {
    e.init();
}
while(running) {
    for(s : sysList) {
        for(e : eList) {
            e.givePriorityTo(s);
        }
        s.staticUpdate();
        for(e : eList) {
            e.update();
            e.returnPriority(s);
        }
    }
}



As you can see, the above is long winded and scary, so I'm hoping this isn't the only solution... Actually, I'm not even sure if the above code could work? Just trying
public interface afddfas {
    public void update();
    static public void staticUpdate();
}


gives me an error in NetBeans, same if I changed it to a abstract class.. So even this won't work... I'm not sure where to go from here... Even though its wrong, it seperates the 3rd party systems from the entitys, and allows for the data to be shared. However, it seems messy. Yeah, so how to I achieve the ends I want? And preferably in a nice way... Cheers, Daniel [Edited by - Zeophlite on September 11, 2009 9:24:41 AM]

Share this post


Link to post
Share on other sites
Hi Zeophlite! I don't have much time, I'll try to write you a little more later. It's a very interesting question! I would maybe use patterns 'Adapter' and 'Observer' together. Check out this book, or a newer version if you can find it.

And now a little example. It's in C#, which doesn't have keywords 'implements' and 'extends', instead it uses ':' for both interfaces and classes.


using System;
using System.Collections.Generic;
using System.Drawing;

namespace Physics
{
class Body
{
public double x;
public double y;
public double speedX;
public double speedY;
public double mass;

public override string ToString()
{
return "I am a Body.\nMy mass is " + mass + ", my position is (" +
x + ", " + y + ").\nMy velocity is (" + speedX + ", " +
speedY + ").";
}
}
}

namespace Rendering
{
class Shape
{
public float positionX;
public float positionY;
public Color color;

public override string ToString()
{
return "I am a Shape.\nMy color is (" + color.A + ", " + color.R +
", " + color.G + ", " + color.B + ").\nMy position is (" +
positionX + ", " + positionY + ").";
}
}
}

class Entity
{
public List<EntityReader> observers = new List<EntityReader>();

private double posX;
private double posY;
private double velX;
private double velY;
private double weight;
private Color color;

// Set methods

public void SetPosX(double posX)
{
this.posX = posX;
UpdateObservers();
}

public void SetPosY(double posY)
{
this.posY = posY;
UpdateObservers();
}

public void SetVelX(double velX)
{
this.velX = velX;
UpdateObservers();
}

public void SetVelY(double velY)
{
this.velY = velY;
UpdateObservers();
}

public void SetWeight(double weight)
{
this.weight = weight;
UpdateObservers();
}

public void SetColor(Color color)
{
this.color = color;
UpdateObservers();
}

// Get methods

public double GetPosX()
{
return this.posX;
}

public double GetPosY()
{
return this.posY;
}

public double GetVelX()
{
return this.velX;
}

public double GetVelY()
{
return this.velY;
}

public double GetWeight()
{
return this.weight;
}

public Color GetColor()
{
return this.color;
}

// Observable object methods

public void AddObserver(EntityReader obs)
{
observers.Add(obs);
}

private void UpdateObservers()
{
foreach (EntityReader obs in observers)
{
obs.ReadEntity(this);
}
}
}

interface EntityReader
{
void ReadEntity(Entity e);
}

class BodyAdapter : EntityReader
{
private Physics.Body body;

public BodyAdapter(Physics.Body body)
{
this.body = body;
}

public void ReadEntity(Entity e)
{
body.x = e.GetPosX();
body.y = e.GetPosY();
body.speedX = e.GetVelX();
body.speedY = e.GetVelY();
body.mass = e.GetWeight() / 9.806;
}
}

class ShapeAdapter : EntityReader
{
private Rendering.Shape shape;

public ShapeAdapter(Rendering.Shape shape)
{
this.shape = shape;
}

public void ReadEntity(Entity e)
{
shape.positionX = (float)e.GetPosX();
shape.positionY = (float)e.GetPosY();
shape.color = e.GetColor();
}
}

class Program
{
static void Main(string[] args)
{
Physics.Body b = new Physics.Body();
Rendering.Shape s = new Rendering.Shape();

BodyAdapter ba = new BodyAdapter(b);
ShapeAdapter sa = new ShapeAdapter(s);

Entity e = new Entity();
e.AddObserver(ba);
e.AddObserver(sa);

e.SetPosX(23.12);
e.SetPosY(14.45);
e.SetVelX(3.16);
e.SetVelY(4.21);
e.SetWeight(455.20);
e.SetColor(Color.FromArgb(255, 128, 64, 32));

System.Console.WriteLine(b.ToString() + "\n");
System.Console.WriteLine(s.ToString() + "\n");
System.Console.ReadLine();
}
}












Hope that was helpful! Bye and good luck!

Dario

[Edited by - damix911 on September 11, 2009 7:53:12 AM]

Share this post


Link to post
Share on other sites
Cheers damix911,

Thanks, I'll read up on Observer and Adapter. Reading through your code, it seems to be quite similar to what I guess, although much better structured, and actually working. I think I'll try to implement what you wrote in Java.

I was wondering if there were any other way to reach the same goal?

Share this post


Link to post
Share on other sites
I'd keep things simple if I were you. If swapping the physics engine should be easy, then don't access the physics body's variables directly, but hide that behind a function:
public class Entity {
init(world) {
b = new Body(startx, starty, width, height);
world.add(b);
}

Rectangle getDimension() {
return new Rectangle(b.x, b.y, b.w, b.h);
}

draw(g) {
Rectangle dimension = getDimension();
g.fillRect(dimension.x, dimension.y, dimension.width, dimension.height);
}

Body b;
}


Now, you only need to swap the physics calls and the implementation of getDimension() (you may want to optimize this by reusing a Rectangle instance, so it doesn't have to create a new object on every call).

As for scripting, it depends on what access those scripts would have, but you can, again, encapsulate certain behaviour in functions (setPosition, setSize, etc.). This keeps changes as local as possible, without too much structural overhead. Throwing massive design patterns at it will likely only obfuscate what you're doing, and it'll make future changes more difficult. Simple code is often simple to modify. ;)

Share this post


Link to post
Share on other sites
Thanks for your input Captain P, but I don't think I explained the problem clearly enough.

My concern isn't about just having 2 systems (draw and physics), but about the future architecture. Each system has its own internal copy of the information - ideally, they would share the one variable, but because they systems are separate, when the physics wants to change the position, the scripting needs to know it as well.

I believe now (after a good night's sleep), that using the adapter (to provide a consistent method to extract the necessary information) and the observer (to update each system's internal version when one changes) is the best solution. The problem with my original, as well as the one you suggested, Captain P, is that the entity is too tightly bound to the physics - ideally, these should be separate. What if I have an entity that doesn't act as a physics object would? Say a stationary, decorative cloud. I could just derive from Entity, and change getDimension such that it gives a static rectangle. Or I could derive from Entity and not include the physics adapter.

My original code seems hacky - there and it works, so leave it. But this adapter/observer seems to be production level architechture. Is this (or something similar) what is used for larger scale games?


Cheers,

Daniel

Share this post


Link to post
Share on other sites
Hi Zeophlite,

I'm happy this pattern may be ok for you; I don't know if it's used for games, there may be other good options. One of the problem with this pattern is that every time you set a data member an update is sent: you may want to provide also two methods as BeginUpdate() and EndUpdate():


e.BeginUpdate();
e.SetPosX(23.12);
e.SetPosY(14.45);
e.SetVelX(3.16);
e.SetVelY(4.21);
e.SetWeight(455.20);
e.SetColor(Color.FromArgb(255, 128, 64, 32));
e.EndUpdate();





If a 'set' method was called between BeginUpdate() and EndUpdate() then the update is actually sent to the observers. Also, calling BeginUpdate() disables the immediate update at the end of every set method.

With the combination of Adapter and Observer, if you add a new system, for instance something for 3D sound that has the class AudioLib.SoundSource3D, you only have to create a SoundSource3DAdapter.

Pattern Observer is a common choice for GUIs; think to a complex audio mixing panel, in which you have a lot of buttons, switches, equalizers, in some cases you may also have different controls that actually modify the same parameter. For instance volume could be controlled with a slider, with some sort of rotating wheel and also with two items in the application Menu. What you can do is to create a numeric variable somewhere, which represents the volume. This variable should be put inside an 'Observable' object, which should also mantain a list of 'Observers'; when the variable is modified by using the methods of the observable object, the latter sends updates to all the observers in the list. The observers are the GUI controls, which can finally redraw.

Pattern Adapter is used in some database systems, to uniform different vendors interfaces to the same standard interface. It's the only one of the classical patterns that exists in two flavours, class-based and object-based. The object-based one is considered more flexible.

Cheers,

Dario

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this