Opinions wanted: Syntax of NxOgre 0.6+

betajaen

05-06-2006 17:23:24

When 0.5 is released, NxOgre will be forked into a separate project for version 0.6 which is a rewrite, although I'll be handing out patches, fixes and new things between 0.5.1 - 0.5.9.9.


Anyway, 0.6 among other things will have a massive chemistry sub-system, as well as what I consider quite daring, new syntax and classes.

So here we go:


mWorld = new World(mRoot, mSceneMgr);

Scene *mScene = mWorld->createScene();
mScene->hasGravityAndFloor();

StaticBodies mArena;
mArena.put( FromModel("wall1"), At(10, 5, 0) );
mArena.put( FromModel("wall1"), At(-10, 5, 0) );
mArena.put( FromModel("plank"), At(5, 5, 0, -3, 40, 0 ) );
mArena.put( new StaticBodyBluePrint("girder.mesh", new cubeShape(0.3,3,0.3), Materials::IRON.), At(3,3,3) );
mScene+=mArena;

RigidBody *myCube = mScene->createBody("myCube", FromMdl("cube"), At(0,10,0));

myCube=+Force(1,0,0);
myCube=+Torque(1,0,0);

RigidBodies mSwarm;

for (int i=0;i < 32;i++) {
mSwarm.put(
FromModel("cube"),
At(0,i * 2.2, 3),
Variant("Density: " + StringConverter::toString(i * 100))
);
}

mScene+=mSwarm;


YAML will be replacing the XML NxScene parts, as well as handling "Variants" for blueprints.

Model is a file format I've been working on in secret, which across between Ogre Mesh/Material/Textures and part body,shapes and states.

Anyway, I'd like some opinions on this, baring in mind this stuff won't be out until like November probably.

ColeZero

05-06-2006 17:31:56

Model is a file format I've been working on in secret, which across between Ogre Mesh/Material/Textures and part body,shapes and states.

That sound interessting, is there more information about that?

And the Force(1,0,1) is also good, because its shorter..^^

betajaen

05-06-2006 17:33:10

I'm sort of keeping secret about it but that sentence sums it up, but it should be around in time for 0.6.

praetor

05-06-2006 17:56:09

I like it, and here is why:

All the things you are doing in the example, and in the general use of NxOgre, will be largely setup. Create the scene, setup the bodies, etc. Even in the middle of running you set up forces and such. These are declarative tasks, and it looks better with a declarative syntax. This is what you have there.

rUmbl3

05-06-2006 18:22:27

looks quite nice.
but i dont like the new scene creation .. the old one is much more suitable for me. because for every map i am loading i create a new scene manager and a new scene ...

ColeZero

05-06-2006 18:29:00

I do also like the syntax, it seems to be time-saving.
Its also clearer, but hopefully the hasGravityandFloor() to not totally replace the hasFloor() and hasGravity() - methods, because an app doesn't always has both..
So the class StaticBodyBluePrint is very long to write, unless you have IntelliSense.^^

betajaen

05-06-2006 18:42:47

but i dont like the new scene creation .. the old one is much more suitable for me. because for every map i am loading i create a new scene manager and a new scene ...

How it works is, that the mSceneMgr passed onto World is optional, if one is passed on; any Scene (like shown there) created without passing on a SceneMgr uses the default world one.

The name is also optional as can be randomly generated, but since you may just have one scene, the name isn't really important.

So you can do:


mWorld = new World(mRoot, mSceneMgr);
mWorld->createScene();


or


mWorld = new World(mRoot);
mWorld->createScene("Main",mSceneMgr);
mWorld->createScene("Second", mSecondSceneMgr);


Its also clearer, but hopefully the hasGravityandFloor() to not totally replace the hasFloor() and hasGravity() - methods, because an app doesn't always has both..

Yep, they'll be in there too. :D

praetor

05-06-2006 21:09:11

Regarding rewrite and interfaces, especially where clarity is key, I figured this was as good a place as any to mention it.

I believe it was you betajaen that looked into imbuing values with units, right? This might interest you:

http://lists.boost.org/boost-announce/2006/05/0093.php

It's a new library under review for inclusion in boost. I downloaded the source and looked at it, and it is indeed quite a nice little packaged. All values are now "linked" to units, with COMPILE-TIME checking of proper units and COMPILE_TIME conversion between unit types. I have no idea if you're still interested in this kind of idea, but this intrigued me at the very least.

betajaen

05-06-2006 21:29:00

Thanks for that. I shall have a look later on and try it out.

[Edit]

I've briefly read the PDF, and I want. But the only thing that worries me is adding boost or portions of it.

Wretched_Wyx

06-06-2006 01:34:47

I really like that new syntax. It cleans things up (on an what is already clean) and makes things quick, while still leaving lots of options. The addition of a YAML script system, and the model format is also great, big feature items for sure.

betajaen

06-06-2006 09:55:14

Alright, I've refined it a little, removed the for loop and it's more "iteratory":


class myStateMachine : public stateMachine {

bool process(state &_state, body *_body) {

if (_state.type == "on_fire" && _state.process = started) {
// Get out the matches.
}

if (_state.type == "on_fire" && _state.process = clear) {
// Put out the fire now.
}

};

};


void setup() {

World* mWorld = new World(mRoot, mSceneMgr);
// or: World(mRoot);

Scene* mScene = mWorld->createScene();
// or: mWorld->createScene("Main", mSceneMgr);

mScene->hasGravityAndFloor();
mScene->loadFromYAML("basicscene.yaml");
mScene+=new myStateMachine();


RigidBodies mSwarm;
mSwarm.amount = 32;
mSwarm.set(FromModel("Cube"), At(0,0,0));
mSwarm.variant(
"name: swarm"
"y: previous_sibling.y add 0.5"
);
mSwarm.state("no_gravity, on_fire");

mScene+=mSwarm;
// or: mScene->add(mSwarm);

RigidBodies mSwarmBodies = myScene->findBodiesLike("swarm");

while(RigidBody b = mSwarmBodies->next() != NULL) {
b+=Force::asKilograms(1000,100,10);
}


RigidBody *myCube = mScene->createRigidBody(
"myCube",
fromModel("cube"),
At(3,0,3);
);

myCube+= Force::asNewtons(10,0,0);
// or: myCube+=Force(10,0,0);
// or: myCube->addForce(10,0,0);

myCube+= Torque(0,500,0);
// or: myCube->addTorque(10,0,0);
}

praetor

06-06-2006 14:47:35


I've briefly read the PDF, and I want. But the only thing that worries me is adding boost or portions of it.


Yeah, if you don't already have boost as a dependency this can be a stumbling block. They usually produce libraries that are interdependent. I'm pretty sure this PQS library is dependant on the current boost numeric conversion routines, which are probably dependant on other portions of boost.... may be too much hassle if you JUST was PQS. But I did find it very interesting and agree totally with the author that it makes code easier to read and write. Plus all the real work can be done at compile time with the compiler doing your checking for you. I thought that was neat. No more assigning a Force to a Torque or acceleration or velocity again!

betajaen

07-06-2006 01:08:05

All right, I've had a little go at doing my own Units system, using the Cake Vector and Quaternions as a base. So here it is:-


#include <iostream>
#include "NxOgre_Unit_At.h"
#include "NxOgre_Unit_Vector.h"
using namespace NxOgre;

int main (int argc, char * const argv[]) {

At l(10,11,12);

l+= NxOgre::Unit::Vector(1,2,3);

std::cout << "l is = " << l << std::endl;
std::cout << "I am at " << At(30,20,30) << std::endl;
return 0;
}


Outputs:


l is = [11, 13, 15|1, 0, 0, 0]
I am at [30, 20, 30|1, 0, 0, 0]


Not to shabby. I should point out that using Vectors and Quaternions directly are now to be discouraged and you should use the "At" class instead which is both a Vector and a Quaternion.

So, your car is At 20,10,20 in position and 1,0,0,0 as rotated.

Now, I have some idea how to overload operators, I'll have a go on some more complex things such as adding bodies to a scene.

(Obviously this isn't NxOgre code, it's just a few files in XCode I'm working with)

betajaen

07-06-2006 11:42:50

Okay, here is an improvement on the above:


RigidBody* rb = new RigidBody("myCube");

// Set the body's position
rb >> At(-1.3,0.5,3.2);

// Translate it up 1 metre Y.
rb += Unit::Vector(0,1,0);

// Add some force.
rb += Force(1.023, 2.28, -2.382);

// Print out the body's name.
std::cout << rb << std::endl;



Which produces:-


Setting Position: [-1.3, 0.5, 3.2|1, 0, 0, 0]
Translating to: [-1.3, 1.5, 3.2|1, 0, 0, 0]
Added Force: 1.023, 2.28, -2.382
myCube


Personally, I think it's quite readable.

[Stealth-Ninja-Edit]

Methods like addForce, or setPosition would obviously still be available. This is for people who like semantics and write in short hand.

[/Stealth-Ninja-Edit]

praetor

08-06-2006 14:30:41

The use of the >> operator doesn't seem very intuitive. Is that because you are dealing with pointers and can't get the = operator to work?

betajaen

08-06-2006 14:39:18

Yep, you caught me. :(

I couldn't get the "operator=" to work like that for some reason.

praetor

08-06-2006 21:25:46

yeah... operator= has to be a "non-static member function." And, since in the example your type is a pointer, that just isn't going to happen. A technique I've used to control copying and lifetime is to enclose my data in a stack-based "holder." So, you'd have something like this:


RigidBody rb("myCube");
rb = At(0,0,1);


RigidBody isn't just doing things on the stack normally though. It's only data is a pointer (i use a smart pointer actually) to an internal data class RigidBodyImpl. This class has all the meat of the RigidBody class. You can now control when RigidBody defs get copied, or just "shared." For instance:


RigidBody rb2 = rb;


Would just get a copy of the original's pointer, not have to actually copy the whole thing. If you use smart pointers to hold the data this all works easily and elegantly. At the same time, dynamic allocation still works, so inheritance works as well. If you make the RigidBodyImpl class public, you can override it. Make a RigidBody constructor that take RigidBodyImpl classes and you have a "holder" that can implement all the syntactic sugar like operators, and the "meat class" that does the dirty work all in one.

praetor

08-06-2006 21:28:42

If you really want to dig into this technique, go to http://www.sourceforge.net/project/odysseyengine

Get the source for the latest release of Odyssey. Check out in particular Config and Definition. More complicated examples including subclassing the internal class are in Variable. God help you when you actually open variable.h though...

betajaen

08-06-2006 21:39:33

Intresting, I couldn't get into the SVN repository (SF was giving me an error), and you got the link wrong :P

But am I guessing does it go a bit like this?



class RigidBody {
public:
RigidBody(string name);
void explode() {
impl->explode();
}

private:
RigidBodyImpl *impl
};


class RigidBodyImpl {

public:
RigidBodyImpl(string _name);
void explode();
private:
string name;
};



If it does, very good thinking! It's a little hacky, but I'd have weight the pro's and con's, to see if changing it like that for the operators would be worth it.

praetor

08-06-2006 21:44:24

It's like the PIMPL idiom. Except not.

Sorry about the link should be "projects" instead of "project." And I may not have anonymous SVN turned on, I'll have to look into that...

I just thought of something: I pay a price here because this uses dynamic allocation. So theres that cost to consider. I'll either try to do some custom pool allocations, or just watch how often I use the classes that use this technique. But, it would seem most of the uses for this system would be allocating most of the objects in the beginning. Just something to think about if new objects are created every frame.

betajaen

08-06-2006 21:52:13

That's the problem, NxOgre would probably create new objects per frame, which then slow it down.

I just wish typedef could just accept regular expressions then I could get around it. :)

Curious is it just the = operator which only works with non-static member functions, i.e. pointers?

praetor

12-06-2006 14:12:49

Probably not actually. But I'd have to check. I'm sure there's some sort of chart somewhere with which operators go where....

betajaen

12-06-2006 14:48:04

I've been writing the YAML Serialiser, called CamelYaml.

I've made a very good clean start with it, but I'm trying to write a polymorph type of variable (which would in turn hold many other polymorph types of variables).

I have it working so far like a proper class:

Node being the parent, and Scalar being child.


Scalar* s = new Scalar("Hello","Monkey");
Node* n;
n=s;
Scalar* y = new Scalar(n);


Which is fine, it works, but I'd rather have it like so:-


Scalar s("Hello", "Monkey");
Node n=s;
Scalar s=n;


But I've tried and tried last night and today, to get it working through operators and castings and everything, but it seems that C++ wasn't built that way.

The reason why I want it to inherit Node, is that I have a vector< Node > that I can shove them into at will, or does Vector have a magic shove any type of variable it can function sort of way?

praetor

12-06-2006 16:27:08

Once you place a child class instance into the vector<Node> slicing occurs. The whole point of the inheritance is lost. The only thing I can suggest is some template trickery, or generator functions.

What does a Scalar have that a Node doesn't? Is it just data? You could parameterize the data portion as a seperate template policy. Then on copies (using = operator) between the two you can selectively copy the data policy. The data policy for a Node would obviously be empty.

praetor

12-06-2006 16:43:56

If you were just looking for storing data, and converting to and from Node to Scalar, could you provide a copy ctor for Scalar and Node and an = operator for both in each class? Then they could each copy back and forth.

betajaen

12-06-2006 17:09:49

Hmmm...Templates I have not dealt with them yet, and I tried the ctor and operator= way, and refused to compile.

[Edit]

Right here, is the "Node" class:


class Node {

public:

void* v;

enum type {
SCALAR,
SEQUENCE,
MAPPING,
UNKNOWN
};

virtual type getType(){return UNKNOWN;}
virtual void printValue() { std::cout << v << std::endl;}

operator Scalar (void);

};


Then the Scalar class:-


typedef std::pair< std::string, std::string > ScalarValue ;

class Scalar: public Node {

public:

ScalarValue v;

Scalar() {
v.first = "NULL";
v.second = "NULL";
}

Scalar(Node *n) {

Scalar *d = dynamic_cast< Scalar* >(n);
v.first = d->v.first;
v.second = d->v.second;
}

Scalar(std::string key, std::string value) {
v.first = key;
v.second = value;
}


Node::type getType() {
return Node::SCALAR;
}

void printValue() {
std::cout << v.first << ":" << v.second << std::endl;
}



};



The ScalarValue typedef is very temporary it'll be replaced with a more elaborate system, hence why I'm using a class to store it all.

[2 hour later edit]

Just been learning the very basics of class templates. They are good, confusing but I can see how they can be very useful.

praetor

12-06-2006 20:50:35

Ok, let me test something out here. Now that I can see what you are trying to do. I don't think templates are necessary yet.

What I would suggest is replacing the void* with an Any variable. Ogre has an Any built-in and is based right off of the boost any. So, it's robust, but you still only depend on boost. It can hold any type, and will throw exceptions if you try to cast to something you didn't put in. You need RTTI on, but you already need that since I see you using a dynamic_cast there.

Anyway, let me try an operator =/copy ctr method.

praetor

12-06-2006 20:53:40

Well, I went back over what you posted, and really it seems that using pointers, new, and possibly some smart pointers is the best way to go here. In this case adding some semantic sugar could get really ugly. That isn't to say specific things can't be smoothed over.

betajaen

12-06-2006 22:05:41

Wow, well that is something:


class CamelSequence: public boost::any {

public:

void addInt(int v) {
boost::any i = v;
l.push_back(i);
}

void addString(const std::string v) {
l.push_back(v);
}


std::list< boost::any > l;

};

int main (int argc, char * const argv[]) {

CamelSequence c;
c.addInt(100);
c.addString("Hello Monkey!");
CamelSequence k;
c.l.push_back(k);

return 0;
}



I think that just spits out polymorph variables right there. Seriously Wow.

betajaen

13-06-2006 00:05:15

Alright, I've progressed further now:

Little piece of code:


Line line(
"# A comment!\n"
"hello: monkey\n"
"how: are you?\n"
"- List Item 1\n"
"- List Item 2\n"
"- List Item 3\n"
"isnt: it a nice day?"
);

line.toConsole();




Courtsey of boost's any produces something quite amazing:



Working on line [0] '# A comment!'
Working on line [1] 'hello: monkey'
Working on line [2] 'how: are you?'
Working on line [3] '- List Item 1'
Working on line [6] 'isnt: it a nice day?'
Done
Contents:-
[0] Scalar {
key='hello'
value='monkey'
}
[1] Scalar {
key='how'
value='are you?'
}
[2] Sequence {
[0] '- List Item 1'
[1] '- List Item 2'
[2] '- List Item 3'
}
[3] Scalar {
key='isnt'
value='it a nice day?'
}


All of these classes being generated of completely different types, are magically stored in the same std::vector< Any >.

If anyone is still puzzled of what this is, it's a [b]YAML parser[/b].

YAML being a text file format almost English in how it's written. If anyone has played with Ruby on Rails you'll know what it is, otherwise check out that link above. I would say in cases like this YAML is superior to XML, because it's lighter and way easier to parse.

I've already written a parser to replace the Ogre.cfg and plugins.cfg, via a Python YAML module through embedding Python into C++ which works fantastically well, but doing it in C++ is a lot faster.

YAML will be used all over in NxOgre 0.6, from describing scenes and bodies, to making adjustments of them via "variants".


[Later edit] - Sneakily added in the sequence and comment bits.

praetor

13-06-2006 03:27:01

Congrats. Looks like stuff is clicking together.

betajaen

13-06-2006 11:01:28

It's really clicking together now. Clicking together so much the syntax is starting to look like a Python/PHP/C++ Hybrid :P


Line line(
"# A comment!\n"
"hello: monkey\n"
"how: 634523\n"
"- List Item 1\n"
"- 12345\n"
"- List Item 3\n"
"isnt: it a nice day?"
);

line.toConsole();



Working on line [0] '# A comment!'
Working on line [1] 'hello: monkey'
Working on line [2] 'how: 634523'
Working on line [3] '- List Item 1'
Working on line [6] 'isnt: it a nice day?'
Done
Contents:-
[0] Scalar {
'hello' => CamelString(6) = 'monkey'
}
[1] Scalar {
'how' => CamelInt = 634523
}
[2] Sequence {
[0] CamelString(11) = 'List Item 1'
[1] CamelInt = 12345
[2] CamelString(11) = 'List Item 3'
}
[3] Scalar {
'isnt' => CamelString(14) = 'it a nice day?'
}

praetor

13-06-2006 14:04:09

I've always had a problem with regular c++ syntax in certain situations. Whenever you define an object, I've always thought c++ syntax ended up being way too verbose. Executing behavior, it looks alright, but definitions need some syntactic sugar.

I have to be honest, I'm not sure what that example shows exactly. I'll probably understand better once I see it in action. Anyway, looks good. Post back with any other goodies. I love a good wringing of c++ for all it's worth.

betajaen

13-06-2006 17:44:22

Basically, it's going line by line. I won't describe it in great detail, but things like "a: b" mean are like a std::pair's, and lines starting with - are lists or std::vector's.

All it's doing it going line by line and figuring out what it is, and what type of variable it is in.

To top it off, instead of saying the content part is a string, it works out is it a integer and shoves it into a integer type, or a floating point, and so on.

I'll be starting the iterator interface now for it all, so it'll probably be better shown in action like you said :D

betajaen

15-06-2006 23:22:04

Alright, I've done a lot more work on this now. But let's show it using an example; a replacement for Ogre.cfg in YAML

This is the file, nice and readable.

# Simple Ogre Configuration
- display: directx9
hertz: 60
width: 1280
height: 1024


Now, once it's read in, it produces a tree of Nodes, based up of "Scalars", "Sequences" and "Mappings".

Scalars are a container of a thing with a label.

hertz: 60


Sequences are lists of things


- Item 1
- Item 2
- Item 3


Mappings are like lists but with keys.


- colors:
- Green
- Blue
- Pink!


So I can iterate through that YAML file as basic nodes via a iterator interface.

But wait it gets better, instead of converting String("hertz: 60") by your self, it splits it up for you.

Hertz is the key.
and 60 is the value.


But wait it gets better as 60 is a number, it'll turn 60 into an integer for you and provide that as the value!

To show it working, I put a little debug dump function in there, this is the result of parsing above:



[0] Mapping = 'String(7:"display") => String(8:"directx9")' {

[0] String(7:"display") => String(8:"directx9")

[1] String(5:"hertz") => Int(60)

[2] String(5:"width") => Int(1280)

[3] String(6:"height") => Int(1024)

}


Not bad, eh?



But wait, YAML gets better, let's include the Plugins and Resources.cfg too!

# Simple Ogre Configuration

display: directx9
hertz: 60
width: 1280
height: 1024
fullscreen: yes
aa: no

resources:
- /meshes/
- /materials/
- /shaders/
- /textures/
- /textures/alpha/

plugins:
- DirectX9
- OpenGL
- CG
- Landscape


CamelYaml can't parse all of that yet, but when it does. I'll be throwing anything to do with XML away!