Jump to content
Rick

Init script callback

Recommended Posts

When your script has a reference to an entity and you need to do things or get data from that entities script, you run into an issue if that scripts Start() hasn't been called yet.

 

It would be nice to have an Init() callback that is called AFTER all entity scripts Start()'s have been called so you can do this.

Share this post


Link to post

One way I have handled this is to have the target object's script set Start to nil in the start function:

function Start()
--do stuff
self.Start = nil
end

 

And then the other script checks if the Start function exists and calls it if it does:

function Start()
if type(self.target.Start)=="function" then
self.target:Start()
end
--do stuff
end

 

You could also make it so it works with any arbitrary script, and Start won't get called twice. This is probably the best way:

And then the other script checks if the Start function exists and calls it if it does:

function Start()
if type(self.target.Start)=="function" then
self.target:Start()
self.target.Start = nil
end
--do stuff
end

Share this post


Link to post

At the end of my level loading process, tell all entities in my scene to start the function "PostStart".

 

 for (const auto& entity : world->entities)

{

entity->CallFunction("PostStart");

}

Share this post


Link to post

@reepblue Yep, this is what should happen from the engine as altering the C++ project doesn't help for the the game launcher. Josh's work around is one way to do it to, but the suggestion was asking more the engine to do it for us.

Share this post


Link to post

I don't see why Reep's workaround wouldn't also work in lua. After map is loaded, iterate over all entities in the world and call PostStart if there is a script and the function exists.

 

EDIT:

Talk is cheap, here is the code. Call this after the map is loaded

 

function CallPostStart()
    local w = World:GetCurrent()
    local e

    for i=0,w:CountEntities()-1 do
         e = w:GetEntity(i)

         if e.script ~= nil and type(e.script.PostStart) == "function" then
             e.script:PostStart()
        end
    end
end

Share this post


Link to post

Yep, you're right. I just woke up when I posted that so wasn't thinking clearly. I used to do something like this a couple years ago and had forgotten.

Share this post


Link to post

One possibility might be to make the engine set script.Start to nil after the function is called the first time. Then you could check if it exists, call it if it does, and know it has been called or doesn't exist if it isn't there.

Share this post


Link to post

One possibility might be to make the engine set script.Start to nil after the function is called the first time. Then you could check if it exists, call it if it does, and know it has been called or doesn't exist if it isn't there.

That would work but I don't like the idea of the engine effectively deleting functions from my table at runtime for which I have no control over. Also you would have to set it to nil before calling script.Start otherwise circular dependencies could cause problems. Not saying circular dependencies are a good idea but they happen. Also its a manual step for the scripter to call script.Start on any and all entities that it depends on being initialized. Imo it's clearer when execution occurred if there is a separate entry point in the script that occurs after every object in the world has already had script.Start called on it.

Share this post


Link to post

 

That would work but I don't like the idea of the engine effectively deleting functions from my table at runtime for which I have no control over. Also you would have to set it to nil before calling script.Start otherwise circular dependencies could cause problems. Not saying circular dependencies are a good idea but they happen. Also its a manual step for the scripter to call script.Start on any and all entities that it depends on being initialized. Imo it's clearer when execution occurred if there is a separate entry point in the script that occurs after every object in the world has already had script.Start called on it.

What if someone expects the Init function of another object to be called already when the Init function of one object is called? Then we will just be back at square one and need another function.

Share this post


Link to post

What if someone expects the Init function of another object to be called already when the Init function of one object is called? Then we will just be back at square one and need another function.

As long as Leadwerks doesn't provide a mechanism to specify the order which entity scripts are loaded, that problem will exist. By adding a script.PostStart function to the life cycle of a script, when execution reaches that point in a script the programmer can assume that any entity script it depends on has at least been allocated and has had a chance to initialize variables to a usable state. Which is more than the programmer can assume now, every script.Start has to assume that nothing exists but itself and has to initialize anything else it depends on manually.

Share this post


Link to post

What if someone expects the Init function of another object to be called already when the Init function of one object is called? Then we will just be back at square one and need another function.

 

That's not what the Init() function is meant for. Start() is called once on map load and is called on entities in no particular order, and Init() is called AFTER all entities are loaded. That's the main point of it. I may need to use script information from a linked entity but I can't inside Start() of another entity. Init() would be used for that specific need.

Share this post


Link to post

Start() is actually called after all entities are loaded, so all entities are there, but not all are guaranteed to have had the Start function called already.

 

This is why Prefab::Load() has an optional NoStartCall. The map loading routine specifies this so that all entities will get loaded first, then it goes through the list of loaded entities and calls Start() for each one:

http://www.leadwerks.com/werkspace/page/api-reference/_/prefab/

Share this post


Link to post

Sorry, I misspoke. After all entities scripts Start() functions are called in which we do initialization of that entity but Init() provides initialization of linked entities if needed. I know you don't like hearing this but Unity has this idea as well because it's a common thing to ask for in such a system.

Share this post


Link to post

I don't like the idea of having two start functions. It's confusing and it doesn't really solve anything because next people will ask for another function called PostInit() when they run into the same situation again in the Init function.

 

If you look in FPSGun.lua file I dealt with it in that script like this:

function Script:Start()
   if self.started then return end
   self.started=true
   --rest of stuff goes here
end

 

And then another script's Start function would call this one's. If the function has already been called then it returns early.

 

You could also have the script remove it's own function like this:

function Script:Start()
   self.Start = nil
   --rest of stuff goes here
end

 

Maybe in the future the engine can go through the Lua tables and call Start() first on entities that are referenced by other entities.

 

Having to manually set the order scripts are executed in is definitely not something I want the user to worry about. Scripts should be written so they just work.

Share this post


Link to post
It's confusing and it doesn't really solve anything because next people will ask for another function called PostInit() when they run into the same situation again in the Init function.

 

This won't happen. All a person needs is a place to init linked entity stuff and that can only happen after all Start() functions are called. There is no need for anything else after that.

 

Having to manually set the order scripts are executed in is definitely not something I want the user to worry about.

 

Yes, I agree that would be a bad idea and with having a post start function solves this :)

 

 

It's fine, looping over all entities after the map is loaded and calling PostStart() if one exists is the workaround I picked and it does what I need.

Share this post


Link to post

I find the default Start function fine for most cases. I only use my PostStart call for sounds. This will prevent any sounds playing while the level is loading.

 

From my experience, Start seems to be called right after the entity and it's component has been made. I recall in a early build of the Vectronic Demo, the spawn sound played while the rest of the map was still loading. (Most noticeable in debug mode) Of course, that was back in 3.5/3.6, and I have been taking my own precautions since!

 

Also, for the record, entity->CallFunction() can be called without a check to see if the entity has a script component attached to it.

Share this post


Link to post

I use an event system where a linked entity would raise an event (call a script function) to the hosting entity script. So I need to subscribe to that linked entities event. Can't do that in Start() since that entities Start() might not have been called yet (Start() is where my event object that handles all of this is created). So having a PostStart() is handy for that. It's a pretty common use case in Unity as well.

 

I find event systems for communication works well and removes needing to know about the inner structure of the linked entity script (just the common event system interface is needed to be know and is common with all entities) and avoid double linking to communicate both ways.

Share this post


Link to post

I find the default Start function fine for most cases. I only use my PostStart call for sounds. This will prevent any sounds playing while the level is loading.

Since Start() is called after the map is done loading, you can start playing sounds in this function and it will work just fine. That is another one of the considerations why I did it this way. It might not have been that way when you started using Leadwerks.

 

Rick, I am interested in some kind of event/state script system, maybe that incorporates coroutines. I don't have a clear concept yet of how this would work but I think if we can figure out something robust and easy to use it would be the next level of Lua scripting in LW.

Share this post


Link to post

I'm guess our terminology isn't lined up when saying event system as my event system wouldn't have a need for coroutines. I'm guessing you mean cinema type system when you say event as that for sure would use coroutines and I've done that before in le too. The term event in my threads context I was just meaning one script fires an event and other scripts can subscribe to said event and get a script function called when that event is raised.

 

Things I've done and used in LE that I think could be helpful:

 

- Cinema system that uses coroutines so the sequence of "events" can be coded in a more natural top down style. i.e. Do this, then this and this at the same time but wait for this, then do that. Etc

- Event system between entity scripts. Would be cool if they were more part of the entity itself and maybe not require linking entities in the editor but can be subscribed between entities in the editor itself. Note that it should allow multiple subscriptions from multiple entities.

- State system where states can be defined in a script to script functions and then you change state and it'll direct the flow to those functions. This helps remove huge nested if statements and just gives an overall better organization to the script.

 

I'll create small demo projects for each system in the way I did them and maybe that'll help give you ideas on them.

Share this post


Link to post

Yeah, I'm saying all those ideas could be integrated into one cohesive system. If you look at my AI code it works, but if we could devise something that does everything that does but is more standardized, it would be really useful for high-level scripting.

 

The door script would be a good candidate for testing because it uses a lot of states.

Share this post


Link to post

From my perspective those 3 systems are very different and used for different things but maybe I'm just not seeing the connection. I was thinking about redoing the soldier script with the state system but it's pretty large and would just take time to learn it's details and replicate it in the state system but maybe the door is easier to do.

 

I still think behavior trees are best for ai. State machines can work for ai but they aren't as flexible so they aren't the ideal in this modern day of games. That's another thing I forgot to list above that I've done in LE and it would be nice to have built in.

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   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...