Load assert, getObjectByUID and "wait until afterLoad()"

0 favourites
  • 12 posts
  • Hi all -

    I'm facing a problem with loading/saving, and I'm not too sure what's triggering an assert.

    The assert is : "Do not call getObjectByUID in loadFromJSON: wait until afterLoad() to look up", and it's coming from preview.js:2863

    Runtime.prototype.getObjectByUID = function (uid_)

    {

    assert2(!this.isLoadingState, "Do not call getObjectByUID in loadFromJSON: wait until afterLoad() to look up");

    var uidstr = uid_.toString();

    if (this.objectsByUid.hasOwnProperty(uidstr))

    return this.objectsByUid[uidstr];

    else

    return null;

    };

    Initially I thought it was to do with objects dynamically created and destroyed by other objects (typically : on created > create some more stuff, on destroyed > destroy some more stuff the was previously created)

    I had a nice repro-case with a pickup object. A pickup item is manually placed in the level ; on creation, the pickup item creates a "floater" item (could be some text, icon, effect, etc. floating above the pickup) ; the pickup keeps a reference (uid) to the floater it creates ; when the pickup item is collected by the player, it is destroyed , which also destroys the floater.

    I moved things around a bit to avoid looking up UIDs in "on destroyed" triggers, and it seems that helped a bit.

    Still, I have more cases of this assert firing when loading game states from a different layout (game menu) :

    I breakpointed on the "return null;" and that never triggered, so I *think* this has actually no impact on the game (and I can't see anything wrong when playing the game after loading with the assert).

    Nevertheless I'm still confused - what's the actual sequence of events when loading a game state, esp. regarding object creation/destruction ?

    Has anyone ever faced this assert when loading save states ? Any idea what might be triggering it ?

    I've been investigating on smaller test scenes and binary chopping through my events, but at this stage I don't have much left to go on and I haven't been able to narrow it down any further ; any pointer or random guess would be appreciated !

  • That's odd, I haven't encountered that. Sounds like a bug since I'd assume no events run during loading. Also "getObjectByUID" isn't just called when using "pick by uid", it's a helper function that's used elsewhere.

    If you put a breakpoint inside the function definition of "assert2" (i think it's in common.js), you could then look at the call stack to see what functions were called before that. Still, that may not give you anything useful.

    If it's your events causing it, i'd be it would be under a trigger. If it happens in the "on created" or "on destroyed" triggers then I'd say it's a bug to report. What other triggers do you use?

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • Thanks for the input, 'really appreciated -

    I tried going through the call stack, but but with limited understanding of the underlying low-level logic it doesn't give me much to go on to be honest ; it's definitely fired during the loading process, and it does seem to have to do with "on created" triggers, at which point it all becomes indices in condition arrays and event blocks and I get a bit lost in the events that are currently running when the assert triggers.

    assert2 (preview_prelude.js:11)

    Runtime.getObjectByUID (preview.js:2863)

    cr.add_common_aces.cnds.PickByUID (commonace.js:475)

    Condition.run_static (eveng.js:1303)

    EventBlock.run (eveng.js:868)

    EventBlock.run_subevents (eveng.js:996)

    EventBlock.run_actions_and_subevents (eveng.js:935)

    EventBlock.run (eveng.js:888)

    Runtime.executeSingleTrigger (preview.js:4843)

    Runtime.triggerOnSheetForTypeName (preview.js:4762)

    Runtime.triggerOnSheet (preview.js:4694)

    Runtime.trigger (preview.js:4665)

    Acts.CallFunction (Function_plugin.js:180)

    Action.run_object (eveng.js:1852)

    EventBlock.run_actions_and_subevents (eveng.js:931)

    EventBlock.run (eveng.js:888)

    Runtime.executeSingleTrigger (preview.js:4843)

    Runtime.triggerOnSheetForTypeName (preview.js:4762)

    Runtime.triggerOnSheet (preview.js:4694)

    Runtime.trigger (preview.js:4665)

    Layout.startRunning (layout.js:381)

    Runtime.doChangeLayout (preview.js:2743)

    Runtime.loadFromJSONString (preview.js:5499)

    Runtime.handleSaveLoad (preview.js:5302)

    Runtime.logic (preview.js:2610)

    Runtime.tick (preview.js:2322)

    Runtime.initRendererAndLoader.tickFunc (preview.js:649)

    As for the triggers I am using, well... Quite a lot ; it's a full platformer game - I'm unable to reproduce the issue on small test scenes, and binary chopping through my events doesn't get me very far in terms of narrowing things down.

  • *edit* Hmm on some further investigation, it seems it might be coming from the system I use to mimic "inheritance" via families and function calls -

    Basically, most of my entities follow this pattern :

    • Entity belongs to Family ; Family is used as an "Interface" to factor some data and logic
    • "on Entity created" > call function "Family_method(Entity.UID)" (almost like a constructor calling a super-constructor to initialise data of higher-level interfaces)

    A concrete example : Guard entity (a simple sprite of a guard patrolling left/right with a sine behaviour) that belongs to my SuspendableIF "interface" family (factored logic I use to suspend/resume entities when far away from the player so they don't cost any performance in my large levels when not close to the action)

    Guard : on created

    set some member data / behaviours (sine magnitude and period)

    calls SuspendableIF_Init(Guard.UID) ; this generates some functions names (Guard_Suspend, Guard_Resume, Guard_Advance) that are cached in the SuspendableIF to be called later (removes the cost of dynamically generating these names from "class" info lookups)

    Anyway, short version : the problem I'm facing might be coming from these UID look-ups in "on created" triggers during the loading phase !

    I might try to summon Ashley on this one to confirm how things actually work in terms of entity creation during loading :

    Ashley : using UID lookups in "on created" triggers when loading game states that cause a layout transition seems to fire a "Do not call getObjectByUID in loadFromJSON: wait until afterLoad() to look up" assert. Does this seem like an expected behaviour ? If it is, are there any good recommendation to easily work around this ?

  • In the meantime I'll try to play around with my "on created" events to try to eliminate/delay all UID lookups. Hopefully this will solve the problem...

  • Confirmed the hunch !

    OK, this is indeed coming from UID lookups in "on created" triggers during loading phases that cause a layout transition.

    I worked around the issue by duplicating a bit of code in all entity creations, or delaying UID references (lazy-ctor style), as I was mostly mimic-ing a super-constructor behaviour via function calls ; this makes it virtually impossible to factor creation logic with "interfaces", but isn't too bad to deal with once you're aware of the limitation.

    Thanks R0J0hound for the input, just boucing some ideas and formulating possible causes to explain the problem help to think properly, in these cases.

    Ashley if I have time I'll try to recreate the assert in a smaller test scene to report it properly as a possible "bug" (or undocumented unexpected behaviour)

  • Yeah, I think it's a bug. The warning is there because asking for an object by its UID when halfway through loading a project is dangerous, because the object you want may not have been loaded yet and therefore not be found. So in the SDK that warning is there to make sure you move your lookup to afterLoad() when all objects are available. But it shouldn't turn up in normal use of the events. So please file a bug report!

  • Thanks for the confirmation, 'much appreciated.

    I've created a minimal test scene demonstrating the problem ; bug reported :

  • Hey Refeuh, i'm still receiving this error on version 216 of C2 and was curious what your workaround exactly was? Specifically the part about delaying the lookup?

    Thanks!

  • No general pattern that worked everywhere tbh, just modifying the logic slightly to demote the dependency, or some kind of lazy init where the entity isn't fully initialised after creation and needs to go through a manual init() process before it gets activated for gameplay.

    In some places, I used to do things like (pseudo-code)

    class Entity : protected SuspendableIF

    {

    Entity()

    {

    SuspendableIF::Suspend(this);

    }

    }

    (calling a factored method from a family acting as interface in the constructor)

    I just changed it by duplicating the factored code SuspendableIF::Suspend() in each "sub-class" ; and I stored specialised function names, i.e. (Entity::s_className)&_Suspend(), as member data to make it accessible in Family/Interface

  • you use a lot of fancy terminology in this last post explaining your solution. I ran into a similar problem after trying to load a layout and in my case I'm picking UIDs because objects are connected in some way, like a head and a body. Would it be a viable work around if I did for example:

    Global var x = 0

    Parent on Created -> spawn child

    ->Parent - set instance var 'identifier' is x +1

    ->Child - set instance var 'identifier' is x +1

    -> set x to x +1

    where X is updated +1 after each spawn use so that it lets each family have a unique identifer that can be called when needed instead of by UID

  • This "bug" made me sad the whole day !

    Happens when :

    • I load a build (Json to array) with many elements (importing a "build" for my character in my game)
    • Then after that, I load a savegame (usually in the same layout, it can be a savegame taken 3 second ago)

    this error occurs at every time.

    or eventually if I play for a long time, filling my array, then reload a game ...

    EDIT : So my thoughts are about the savegame too heavy or something, maybe because of the big array. If I understand well how C2 arrays works, clearing it won't make it smaller. So when I'm not using the array for import / export, I set its size to 0,0,0, then set its size to 17,28,10 when I need it only, and crush it after to 0,0,0 again. Since that, I didn't experience the "wait until afterLoad()" error ... yet ? I Hope this workaround will last...

Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)