Make __dm_script_instance__ part of the API and document it

Very useful feature.
Allows me organize strict update order between script components, re-route messages like ‘ray_cast_response’ to another script instance, greatly reduce amount of messages sent between scripts, etc.

1 Like

I agree it’s useful. I use it for my timers. I wouldn’t overuse it, though… Message passing can be made to work elegantlly.

I propose an API like this:

local instance = script.get_instance()
script.run_as_instance(instance, function ()
  -- Code ran in the context of another script
end

I think it good as it is (as a global variable).
Just mention it in documents and promise it will not go away in the future.

And more slow, especially on mobile.

Any API around it would be great, but I’m of the opinion that APIs should be simple and prevent you from doing mistakes. The general use case for setting the script instance is running something in another script, then setting it back to the caller script. If you forget to set it back, bad things can happen. Semantically, your intent is to “run a piece of code in the context of another script”, hence an API should model that (piece of code = function closure, context of another script = script instance) and should prevent you from doing things you didn’t intend to.

And yeah, message passing has an overhead, so it would be cool to be able to “cheat” the system from time to time. :frowning:

1 Like

There are some cases where you will want to switch context several times, before returning to the original one.

However, these two approaches can coexist as ‘low’ and ‘high’ level API.

Yeah I second this, as of now I’m using my simple timer lib which is based on this global, and it works wonderfully.

I’m taking a chance on it not going away anytime soon, otherwise I’d have to find a new solution to timing things.

1 Like

Can someone from Defold team confirm that this possibility(switch script context and call functions from other scripts ) removed from version 1.2.150 (alpha channel)?

No, it was about to be removed but then we decided against it. (We are doing a lot of optimisations to the internals of Defold at the moment). @inactive-vilse please confirm if the value is staying!

Yes, it is staying for now.

I wonder why you can’t use the provided APIs ”GetInstance” and ”SetInstance”? They just use this constant to get/set the script instance.

Is there something I’m missing here?

But that’s from the extension side right? Dmitry is manipulating the script instance from the Lua side (yes a bit dangerous and undocumented).

Yeah, right, that is really not a great idea but I understand why it is done.

Changing the context of a running script is playing with fire a bit so I’m not sure we should provide an API as it could encourage this kind of solutions.

How hard would it be to write an NE instead? And isn’t the ”timer” api sufficient?

An even more underhanded way would be to create a Native Extension that exposes Set/GetInstance to Lua by using the functions in the native extension SDK…

I think this warrants a discussion in the engine team.

I made little Native Extension library that exposes Get/SetInstance when added.

4 Likes

This is not my case.

I use context switching for the following:

  1. Maintain a strict update order for specific groups of objects.
-- in some dedicated script...
function update(ctx, dt)
...
   for context, callback in next, update_group_motor_platforms do
      __dm_script_instance__ = context
      callback(context, dt)
   end

   for context, callback in next, update_group_crates do
      __dm_script_instance__ = context
      callback(context, dt)
   end

   for context, callback in next, update_group_before_player do
      __dm_script_instance__ = context
      callback(context, dt)
   end

   for context, callback in next, update_group_player do
     __dm_script_instance__ = context
      callback(context, dt)
   end

   for context, callback in next, update_group_after_player do
      __dm_script_instance__ = context
      callback(context, dt)
   end

   for context, callback in next, update_group_cameras do
      __dm_script_instance__ = context
      callback(context, dt)
   end

   for context, callback in next, update_group_lights do
      __dm_script_instance__ = context
      callback(context, dt)
   end

   __dm_script_instance__ = ctx
...
end -- update

This is not achievable by the official API if you want to preserve the correct script context in each update call.

  1. To communicate between scripts (and across different script types: script <-> gui_script <-> render_script) in synchronous manner.
    This is not possible for the messaging system due to its nature. It is also faster(I believe) and more convenient(I am sure) then send messages every time.

Before latest alpha build I mostly use this helper function to call functions in another context:

function execute_in_context(context, fn, ...)
	local current = __dm_script_instance__
	__dm_script_instance__ = context
	local result = fn(context, ...)
	__dm_script_instance__ = current
	return result
end

This is not longer working. Possibly because of some internal engine optimisation?

However, with your extension, this works again:

function execute_in_context(context, fn, ...)
	local current = lua_script_instance.Get()
	lua_script_instance.Set(context)
	local result = fn(context, ...)
	lua_script_instance.Set(current)
	return result
end

Possible usage:

-- in helper module...
local context_store = {}
local function set_context(gameobject, context)
   context_store[gameobject] = context
end
local function get_context(gameobject)
   return context_store[gameobject]
end

-- in some script...
local function some_function(self, a, b)
   -- API calls that may depends of the script instance in which they called
   ...
   return result
end
function init(self)
   self.gameobject = get_id()
   set_context(gameobject, self)
   self.some_function = some_function
end
function final(self)
   set_context(self.gameobject, nil)
end


-- in some other script...
local hit = raycast(ray_start, ray_end, TARGETS)
if hit then
   local context = get_context(hit.id)
   if context and context.some_function then
      local result = execute_in_context(context, context.some_function, param1, param2)
      ...
   end
end

Thanks for the feeback!

As far as I know we the code you quote should still work, I don’t know of any changes in the engine that would break it.

function execute_in_context(context, fn, ...)
   local current = __dm_script_instance__
   __dm_script_instance__ = context
   local result = fn(context, ...)
   __dm_script_instance__ = current
   return result
end
1 Like

Oh, I didn’t see that you were on the alpha branch, will check.

1 Like

Yes, alpha 1.2.150 2fd859599bca6bd008ccb6ead27df6a645de1a99. Not working.

1 Like

Ok, I know what is wrong. The way you get the current script instance still works, but how you set it needs to be changed since we now use a hash as well so the lookup is faster.

I would really recommend that you use the small NE I wrote instead of accessing that variable.

3 Likes

FYI, we will completely remove __dm_script_instance__ in the release 1.2.151. It is already removed in the latest alpha.

If you need to get/set the current script instance use https://github.com/DanEngelbrecht/LuaScriptInstance or roll your own NE.

7 Likes