Five Programming Changes You'll See in Leadwerks 5
Leadwerks 4.x will see a few more releases, each remaining backwards compatible with previous versions. Leadwerks 5.0 is the point where we will introduce changes that break compatibility with previous versions. The changes will support a faster design that relies more on multi-threading, updates to take advantage of C++11 features, and better support for non-English speaking users worldwide. Changes are limited to code; all asset files including models, maps, shaders, textures, and materials will continue to be backwards-compatible.
Let's take a look at some of the new stuff.
Shared Pointers
C++11 shared pointers eliminate the need for manual reference counting. Using the auto keyword will make it easier to update Leadwerks 4 code when version 5 arrives. You can read more about the use of shared pointers in Leadwerks 5 here.
Unicode Support
To support the entire world's languages, Leadwerks 5 will make use of Unicode everywhere. All std::string variables will be replaced by std::wstring. Lua will be updated to the latest version 5.3. This is not compatible with LuaJIT, but the main developer has left the LuaJIT project and it is time to move on. Script execution time is not a bottleneck, Leadwerks 5 gains a much longer window of time for your game code to run, and I don't recommend people build complex VR games in Lua. So I think it is time to update.
Elimination of Bound Globals
To assist with multithreaded programming, I am leaning towards a stateless design with all commands like World::GetCurrent() removed. An entity needs to be explicitly told which world it belongs to upon creation, or it must be created as a child of another entity:
auto entity = Pivot::Create(world);
I am considering encapsulating all global variables into a GameEngine object:
class GameEngine { public: std::map<std::string, std::weak_ptr<Asset> > loadedassets; shared_ptr<GraphicsEngine> graphicsengine; shared_ptr<PhysicsEngine> physicsengine; shared_ptr<NetworkEngine> networkengine; shared_ptr<SoundEngine> soundengine; shared_ptr<ScriptEngine> scriptengine;//replaces Interpreter class };
A world would need the GameEngine object supplied upon creation:
auto gameengine = GameEngine::Create(); auto world = World::Create(gameengine);
When the GameEngine object goes out of scope, the entire game gets cleaned up and everything is deleted, leaving nothing in memory.
New SurfaceBuilder Class
To improve efficiency in Leadwerks 5, surfaces will no longer be stored in system memory, and surfaces cannot be modified once they are created. If you need a modifiable surface, you can create a SurfaceBuilder object.
auto builder = SurfaceBuilder::Create(gameengine); builder->AddVertex(0,0,0); builder->AddVertex(0,0,1); builder->AddVertex(0,1,1); builder->AddTriangle(0,1,2); auto surface = model->AddSurface(builder);
When a model is first loaded, before it is sent to the rendering thread for drawing, you can access the builder object that is loaded for each surface:
auto model = Model::Load("Models\box.mdl", Asset::Unique); for (int i=0; i<model->CountSurfaces(); ++i) { auto surface = model->GetSurface(i); shared_ptr<SurfaceBuilder> builder = surface->GetBuilder(); if (builder != nullptr) { for (int v=0; v < surface->builder->CountVertices(); ++v) { Vec3 v = builder->GetVertexPosition(v); } } }
98% of the time there is no reason to keep vertex and triangle data in system memory. For special cases, the SurfaceBuilder class does the job, and includes functions that were previously found in the Surface class like UpdateNormals(). This will prevent surfaces from being modified by the user when they are in use in the rendering thread.
A TextureBuilder class will be used internally when loading textures and will operate in a similar manner. Pixel data will be retained in system memory until the first render. These classes have the effect of keeping all OpenGL (or other graphics API) code contained inside the rendering thread, which leads to our next new feature...
Asynchronous Loading
Because surfaces and textures defer all GPU calls to the rendering thread, there is no reason we can't safely load these assets on another thread. The LoadASync function will simply return true or false depending on whether the file was able to be opened:
bool result = Model::LoadASync("Models\box.mdl");
The result of the load will be given in an event:
while (gameengine->eventqueue->Peek()) { auto event = gameengine->eventqueue->Wait(); if (event.id == Event::AsyncLoadResult) { if (event.extra->GetClass() == Object::ModelClass) { auto model = static_cast<Model>(event.source.get()); } } }
Thank goodness for shared pointers, or this would be very difficult to keep track of!
Asynchronous loading of maps is a little more complicated, but with proper encapsulation I think we can do it. The script interpreter will get a mutex that is locked whenever a Lua script executes so scripts can be run from separate threads:
gameengine->scriptengine->execmutex->Lock(); //Do Lua stuff gameengine->scriptengine->execmutex->Unlock();
This allows you to easily do things like make an animated loading screen. The code for this would look something like below:
Map::LoadASync("Maps/start.map", world); while (true) { while (EventQueue::Peek()) { auto event = EventQueue::Wait(); if (event.id == Event::AsyncLoadResult) { break; } } loadsprite->Turn(0,0,1); world->Render();// Context::Sync() might be done automatically here, not sure yet... }
All of this will look pretty familiar to you, but with the features of C++11 and the new design of Leadwerks 5 it opens up a lot of exciting possibilities.
- 5
18 Comments
Recommended Comments