GoPositionSetter Native Extension

GoPositionSetter Native extension

Fast way to update position for thousands of game objects.

30000 bunnies 53.71 fps

30000 bunnies 67.69 fps

Tested on my PC.

What changed and why it’s faster

function update(self, dt)
	bunnymark.update()
	local bunnies = bunnymark.bunnies
	for i=#bunnymark.bunnies,1,-1 do
		local bunny = bunnies[i]
		bunny.velocity = bunny.velocity - 1200 * dt

		local p = bunny.position
		p.y = p.y + bunny.velocity * dt
		if p.y < 50 then
			p.y = 50
			bunny.velocity = -bunny.velocity
		end
		--go.set_position(p, bunny.id)--this is used in update single
	end
	self.position_setter:update() --this is used in go_position_setter
	label.set_text("#label", ("Bunnies: %d FPS: %.2f. Click to add more"):format(bunnymark.get_bunny_count(), bunnymark.get_fps()))	
end

It is faster because:

  1. It uses one C++ call instead of thousands of go.set_position calls.
  2. It avoids some checks. You as the developer guarantee that the vector and gameobject are valid and available.
//This is how go.set_position works:
static Instance* ResolveInstance(lua_State* L, int instance_arg)
{
    ScriptInstance* i = ScriptInstance_Check(L);
    Instance* instance = i->m_Instance;
    if (lua_gettop(L) == instance_arg && !lua_isnil(L, instance_arg)) {
        dmMessage::URL receiver;
        dmScript::ResolveURL(L, instance_arg, &receiver, 0x0);
        if (receiver.m_Socket != dmGameObject::GetMessageSocket(i->m_Instance->m_Collection->m_HCollection))
        {
            luaL_error(L, "function called can only access instances within the same collection.");
        }

        instance = GetInstanceFromIdentifier(instance->m_Collection->m_HCollection, receiver.m_Path);
        if (!instance)
        {
            luaL_error(L, "Instance %s not found", lua_tostring(L, instance_arg));
            return 0; // Actually never reached
        }
    }
    return instance;
}

int Script_SetPosition(lua_State* L)
{
    Instance* instance = ResolveInstance(L, 2);
    dmVMath::Vector3* v = dmScript::CheckVector3(L, 1);
    dmGameObject::SetPosition(instance, dmVMath::Point3(*v));
    return 0;
}

//this is how go_position_setter.update() works:
//no checks. Just SetPosition to the instance
void PositionSetterUserdata::update() {
    for (int i = 0; i < instances.Size(); ++i) {
        InstancePositionData instancePositionData = instances[i];
        dmGameObject::SetPosition(instancePositionData.rootInstance, dmVMath::Point3(*instancePositionData.position));
    }
}


How to use

  1. Create a go_position_setter.new() in your collection
self.position_setter = go_position_setter.new()
  1. Add bunny to position_setter
	self.position_setter:add(bunny.id, bunny.position)
  1. Update bunny.position
	bunny.position.y = bunny.position.y + bunny.velocity * dt
  1. Update all positions
	self.position_setter:update()

Credits

Based on Defold bunnymark test GitHub - britzl/defold-bunnymark: Defold bunnymark test
Bunny graphics from PixiJS Bunny Mark

17 Likes