Jump to content
  • entries
    945
  • comments
    5,899
  • views
    930,151

Unit Testing


Josh

8,725 views

 Share

I've spent the last few days writing simple examples for every single command in Leadwerks 3. Not only does this make the documentation more friendly, it also acts as a final test to make sure all the commands work the way they say they should. I make the C++ example and then Chris converts it to Lua (and tells me what I did wrong!).

 

I didn't realize it at first, but this really showcases the strength of API design of Leadwerks. Since you get full control over the execution and flow of a Leadwerks program, it's easy to learn from simple examples that demonstrate one idea. Below are a few examples for different commands in the API.

 

Get the device accellerometer reading:

#include "App.h"

using namespace Leadwerks;

Window* window = NULL;
Context* context = NULL;

bool App::Start()
{
   window = Window::Create();
   context = Context::Create(window);
   return true;
}

bool App::Continue()
{
   if (window->Closed() || window->KeyDown(Key::Escape)) return false;

   Draw::SetColor(0,0,0);
   context->Clear();

   //Display the device information on the screen
   Draw::SetBlendMode(Blend::Alpha);
   Draw::SetColor(1,1,1);
   Draw::Text("Orientation: "+String(Device::GetOrientation()),2,2);
   Draw::Text("Acceleration: "+Device::GetAcceleration().ToString(),2,22);
   Draw::SetBlendMode(Blend::Solid);

   context->Sync();        
   return true;
}

 

Create a physics shape from a model and use it on a scaled entity :D :

#include "App.h"

using namespace Leadwerks;

Window* window = NULL;
Context* context = NULL;
World* world = NULL;
Camera* camera = NULL;

bool App::Start()
{
   window = Window::Create();
   context = Context::Create(window);
   world = World::Create();
   camera = Camera::Create();
   camera->SetRotation(35,0,0);
   camera->Move(0,0,-10);
   Light* light = DirectionalLight::Create();
   light->SetRotation(35,35,0);

   //Create the ground
   Model* ground = Model::Box(10,1,10);
   ground->SetPosition(0,-0.5,0);
   ground->SetColor(0,1,0);

   //Create a shape
   Shape* shape = Shape::Box(0,0,0, 0,0,0, 10,1,10);
   ground->SetShape(shape);
   shape->Release();

   //Load a model
   Model* model = Model::Load("Models/teapot.mdl");
   model->SetPosition(0,0,0);
   model->SetColor(0,0,1);
   model->SetScale(4,4,4);

   //Create a shape
   shape = Shape::PolyMesh(model->GetSurface(0));
   model->SetShape(shape);
   model->SetPosition(0,0,0);
   shape->Release();

   //Create some objects to fall
   model = Model::Sphere();
   shape = Shape::Sphere();
   model->SetShape(shape);
   shape->Release();
   model->SetMass(1);
   model->SetColor(Math::Rnd(0,1),Math::Rnd(0,1),Math::Rnd(0,1));
   model->SetPosition(Math::Rnd(-1,1),Math::Rnd(3,6),Math::Rnd(-1,1));

   for (int i=0; i<10; i++)
   {
       model = (Model*)model->Instance();
       model->SetCollisionType(Collision::Prop);
       model->SetColor(Math::Rnd(0,1),Math::Rnd(0,1),Math::Rnd(0,1));
       model->SetPosition(Math::Rnd(-1,1),5+i*2,Math::Rnd(-1,1));
   }

   return true;
}

bool App::Continue()
{
   if (window->Closed() || window->KeyDown(Key::Escape)) return false;

   Time::Update();
   world->Update();
   world->Render();
   context->Sync();

   return true;
}

 

Or in Lua, if you prefer:

function App:Start()

 

self.window=Window:Create(self.title,0,0,1136+6,640+32,Window.Titlebar+Window.Center+8)

self.context=Context:Create(self.window,0)

self.world=World:Create()

camera = Camera:Create()

camera:SetRotation(35,0,0)

camera:Move(0,0,-10)

light = DirectionalLight:Create()

light:SetRotation(35,35,0)

 

--Create the ground

ground = Model:Box(10,1,10)

ground:SetPosition(0,-.05,0)

ground:SetColor(0,1,0)

 

--Create a shape

shape = Shape:Box(0,0,0, 0,0,0, 10,1,10)

ground:SetShape(shape)

shape:Release()

 

--Load a model

model = Model:Load("Models/teapot.mdl")

model:SetPosition(0,0,0)

model:SetColor(0,0,1)

model:SetScale(4,4,4)

 

--Create a shape

shape = Shape:PolyMesh((model:GetSurface(0)))

model:SetShape(shape)

model:SetPosition(0,0,0)

shape:Release()

 

--Create some objects to fall

model = Model:Sphere()

shape = Shape:Sphere()

model:SetShape(shape)

shape:Release()

model:SetMass(1)

model:SetColor(Math:Rnd(0,1),Math:Rnd(0,1))

model:SetPosition(Math:Rnd(-1,1),Math:Rnd(3,6),Math:Rnd(-1,1),true)

 

for i=0, 9 do

model = tolua.cast(model:Instance(),"Model")

model:SetCollisionType(Collision.Prop)

model:SetColor(Math:Rnd(0,1),Math:Rnd(0,1),Math:Rnd(0,1))

model:SetPosition(Math:Rnd(-1,1),5+i*2,Math:Rnd(-1,1),true)

end

return true

end

 

function App:Continue()

 

if self.window:Closed() or self.window:KeyHit(Key.Escape) then return false end

 

Time:Update()

self.world:Update()

self.world:Render()

self.context:Sync()

 

return true

end

 

Create a texture from scratch:

#include "App.h"

using namespace Leadwerks;

Window* window = NULL;
Context* context = NULL;
World* world = NULL;
Texture* texture = NULL;

bool App::Start()
{
   window = Window::Create();
   context = Context::Create(window);

   //Create a texture
   texture = Texture::Create(256,256);

   //Set the texture pixel data
   char* pixels = (char*)malloc(texture->GetMipmapSize(0));
   char r,g,b;
   for (int x=0; x<256; x++)
   {
       for (int y=0; y<256; y++)
       {
           int p = (x*texture->GetWidth() + y)*4;
           memcpy(&r,pixels + p + 0, 1);
           memcpy(&g,pixels + p + 1, 1);
           memcpy(&b,pixels + p + 2, 1);
           if (x<128)
           {
               if (y<128)
               {
                   r=0; g=0; b=255;
               }
               else
               {
                   r=255; g=0; b=0;
               }
           }
           else
           {
               if (y<128)
               {
                   r=255; g=0; b=0;
               }
               else
               {
                   r=0; g=0; b=255;
               }                
           }
           memcpy(pixels + p + 0, &r, 1);
           memcpy(pixels + p + 1, &g, 1);
           memcpy(pixels + p + 2, &b, 1);
       }
   }
   texture->SetPixels(pixels);

   return true;
}

bool App::Continue()
{
   if (window->Closed() || window->KeyDown(Key::Escape)) return false;

   Draw::SetColor(0,0,0);
   context->Clear();

   //Display the texture on screen
   Draw::SetColor(1,1,1);
   Draw::Image(texture,0,0);

   context->Sync();        
   return true;
}

  • Upvote 5
 Share

37 Comments


Recommended Comments



Always use sizeof() with memcpy(), else your code cannot be compiled as 64-bit and on other platforms which have different type sizes.

  • Upvote 1
Link to comment

The SetPixels() code depends on the data being a known size because that's what the OpenGL texture format dictates. Is there actually any platform that doesn't treat a char as one byte?

Link to comment

Oooohhhh classes, my loved classes! Cannot wait to play again on LW! I hope you're going to send a newsletter to the current users when it'll be the time to remember us to buy the upgrade, my memory has some leaks most of time :)

Link to comment

Nice progress but... first sorry to be such a criticism guy but as a c++ programmer i have some issues:

 

1. inconsistent context

Draw::SetColor(0,0,0);
    context->Clear();
    //Display the device information on the screen
    Draw::SetBlendMode(Blend::Alpha);
    Draw::SetColor(1,1,1);
    Draw::Text("Orientation: "+String(Device::GetOrientation()),2,2);
    Draw::Text("Acceleration: "+Device::GetAcceleration().ToString(),2,22);
    Draw::SetBlendMode(Blend::Solid);

 

Why you switch from context to a static class and backwards ? (You should put this Draw:: stuff into the context imho)

 

2. Why you have to release your shape at this

//Create a shape
 shape = Shape::PolyMesh(model->GetSurface(0));
 model->SetShape(shape);
 model->SetPosition(0,0,0);
 shape->Release();

 

In my opinion you shouldn't release the shape (safe it as reference) or release it in the ->SetShape Method.

 

This is just my opinion and it is not ment to be negative.

Link to comment

@Furbolg: About the shape I think Josh's is more usable, in case you need to apply the same shape to more models you'd need to manually create more shapes. Personally I find the current model more usable.

 

YOU should need to know WHEN your object will be released, not whatever method of another class. Just my opinion... smile.png

Link to comment

I agree with the inconsistent context. There seems to just be way to many static things in LE3. If you wanted to do that just make it a C library like LE2 then.

Link to comment

This seems really easy to use, especially commands like Device::GetOrientation() keep things simple, I also agree with ZioRed.

I don't get the idea of the context object, is this something like a render buffer?

Link to comment

@ ZioRed:

Possible but wouldn't it be better to use the stack instead of the heap for this approach ?

 

@CGMan:

No its not (only) a buffer, its a "collection" of states (and some more).

 

Lets say you want to create 2 context objects (editor with 2+ windows), how would you do that ? I mean how can you tell which Draw::SetColor belongs to which context ?

 

@All / Josh:

As i said dont take this as flaming, i just want to share my opinion/experience i made with c++.

Link to comment

I think I can make LE3 much faster, when I even find lots of things to optimize in those samples.

 

For example:

++x instead of x++ uses CPU registers to speed up a lot (however mingw and vs already does this optimization themselves)

4*(y+x*texture->GetWidth()) instead (constants make a big speed different when placed in front, loop variables closer to loop statement allow registers

if(y<128)if(x<128) same here, loop variables closer to loop to utilize registers

 

In most places it won't matter much, if there are no big loops. But when there are big loops, like going through all vertices of a scene, then it might make some notifiable difference.

Link to comment

@ Lumooja: Sure ...

Premature optimization is the root of all evil and the algorithm is more important than this little tricks.

  • Upvote 3
Link to comment

I think I can make LE3 much faster, when I even find lots of things to optimize in those samples.

 

For example:

++x instead of x++ uses CPU registers to speed up a lot (however mingw and vs already does this optimization themselves)

4*(y+x*texture->GetWidth()) instead (constants make a big speed different when placed in front, loop variables closer to loop statement allow registers

if(y<128)if(x<128) same here, loop variables closer to loop to utilize registers

 

In most places it won't matter much, if there are no big loops. But when there are big loops, like going through all vertices of a scene, then it might make some notifiable difference.

Doesnt the compiler optimizes this?

I think the way the engine is done is awesome and that we wont have much performance issues.

Link to comment

VS and mingw does not do the constant-in-beginning-of-expression and loop-variable-closest-to-loop optimizations. Every time I make a test program I get the same results.

Link to comment

Context::Create() is the creation function. You can have separate contexts, so it is necessary to call context->Clear(), not just a static function like "Context::Clear()".

Link to comment

I actually like the idea of using context->DrawText(), etc. though. Internally, the drawing commands have absolutely nothing to do with the context class, but it does make intuitive sense considering that you have context->Clear() and context->Sync().

 

So with a few changes, we get this:

#include "App.h"

using namespace Leadwerks;

App::App() : window(NULL), context(NULL), world(NULL), camera(NULL) {}

App::~App()
{
   delete world;
   delete window;
}

bool App::Start()
{
   window = Window::Create();
   context = Context::Create(window);
   return true;
}

bool App::Continue()
{
   if (window->Closed() || window->KeyDown(Key::Escape)) return false;

   context->SetColor(0,0,0);
   context->Clear();
   context->SetColor(0,1,0);
   context->DrawRect(0,0,100,100);

   context->Sync();        
   return true;
}

  • Upvote 1
Link to comment

That was a good suggestion. Even the Lua code looks nicer.

function App:Start()

self.window = Window:Create()

self.context = Context:Create(window)

return true

end

 

function App:Continue()

if self.window:Closed() or self.window:KeyDown(Key:Escape) then return false end

 

self.context:SetColor(0,0,0)

self.context:Clear()

self.context:SetColor(0,1,0)

self.context:DrawRect(0,0,100,100)

 

self.context:Sync()

return true

end

Link to comment

community optimized code. awesomeness.

@canardian

can you post a quick example of what you mean by moving loop vars closer to loop to use registers.

Link to comment

The problem with putting functions which are not connected to the context, except that they use the current context, is that when a user makes multiple contexts, then for example context3->DrawImage() will still draw to context1, if it's the current context. Of course you could fix this by doing a context3->Activate() if it's not the current context, but that will make things slower, because a if() check is slower than a integer assignment.

  • Upvote 1
Link to comment

For making a game i don't think we need more than one context ?

 

Could we have some example having some basic game loop ?

(like player pushing a key)

Link to comment

The problem with putting functions which are not connected to the context, except that they use the current context, is that when a user makes multiple contexts, then for example context3->DrawImage() will still draw to context1, if it's the current context.

 

I believe Josh can handle this.

 

Of course you could fix this by doing a context3->Activate() if it's not the current context, but that will make things slower, because a if() check is slower than a integer assignment.

Sure an if (asm cmp) will slow down nearly everything because rendering a model / light or playing a sound has so few cpu cycles that every if counts... serious Lumooja, do you listen to yourself ?

 

 

@ xtrempb:

 

I think he means something like this

// "slow"
for(int i = 0; i < vector.size(); ++i)
{
}
// "fast"
int vectorsize = vector.size();
for(int i = 0; i < vectorsize; ++i)
{
}

Link to comment

There is no excuse in good programming practice to not use a faster method when it is as easy to implement as a slow method. When systems grow bigger, all the little speed differences will exponentially sum of, when one function is calling another function multiple times per loop.

Link to comment

"Good programming" doesnt mean to optimize every little if, switch etc. When you use inheritance (which josh does) and maybe use virtual functions (which josh probably does) then this is going to waste much more cycles then you can safe by optimize hundreds of little ifs.

"Good programming" means in my understanding maintainable understandle code (short but precise comments, good parameter names etc.)

 

No it will not exponentially sum up because you do other stuff as well. As i said if you render a model it has much more cpu cycles wasted than your small optimizations can safe.

  • Upvote 2
Link to comment

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...