Cooking failure when using weak_map and construction script

Summary

Been trying to store some objects at construction time but I can’t seem to be able to

Here’s the repro :

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

var SomeStoredObjects : weak_map(session, []some_class) = map{}
GetSomeStoredObjects()<reads>:[]some_class=
    SomeStoredObjects[GetSession()] or array{}

some_class := class:
    block:
        option:
            set SomeStoredObjects[GetSession()] = (SomeStoredObjects[GetSession()] or array{}) + array{Self}

test_weak_maps := class(creative_device):
    SomeObject : some_class = some_class{}
Error Log

We hit an error while cooking this content
errors.com.epicgames.content-service.cook_failure
CookJobId:07d4b88e-379b-44ad-86af-8ab118e5d26c
An error occurred while communicating with the game servers (failed to cook module a127ce0f-4bee-4716-43c8-e99771b187b3 (artifact 50365d91-5893-4310-9a8d-03ec2f3c6cd4:linux-server) (cookJobId 07d4b88e-379b-44ad-86af-8ab118e5d26c) with error code errors.com.epicgames.cookplugin.cookfailure).
LogWindows: Error: appError called: Can’t save ‘C:/Temp/Temp/Cook/Cooked/LinuxServer/FortniteGame/Plugins/GameFeatures/a127ce0f-4bee-4716-43c8-e99771b187b3/Content/_Verse.uasset’: Illegal reference to private object: ‘Level /a127ce0f-4bee-4716-43c8-e99771b187b3/GameManager.GameManager:PersistentLevel’ referenced by ‘Default___Root’ (at ‘/a127ce0f-4bee-4716-43c8-e99771b187b3/_Verse’) in its ‘__verse_0x9D62C0F0_SomeStoredObjects’ property (private object belongs to an external map).

Please select what you are reporting on:

Verse

What Type of Bug are you experiencing?

Stability

Steps to Reproduce

See repro

Expected Result

Shouldn’t crash

Observed Result

Crashes

Platform(s)

PC

Getting someone to take a look.

2 Likes

HI @im_a_lama

One of our devs suggests the following while we investigate and work towards a fix:

the creator can fix by not running this: set SomeStoredObjects[GetSession()] = (SomeStoredObjects[GetSession()] or array{}) + array{Self} inside of the block option

That’s exactly what I do in the example

I’m pretty sure the issue doesn’t come from my syntax, it’s something to do with invalid sessions being accessed at construction time or something like this since it’s crashing when trying to access the map Level (probably the wrong Level)

Sorry, my reply is confusing. The dev suggested that you don’t run that.

FORT-891097 has been ‘Closed’. The issue reported is not caused by a bug in the tool.

That’s a weird answer too tbh, I don’t understand why it’s okay to keep this able to crash, if it crashes then it’s a bug, I don’t have any other way to achieve what I’m trying to do, I’m probably missing a part here

Blocks{} of classes outside entrypoints (OnSimulate/OnBegin) will run at compile, and during that time some stuff are not available yet (such as Session, on this example), that’s why it crashes…

The solution as it was said on a earlier post, is to not run that code inside scopes that can run during compilation, instead, you should make sure it only runs at runtime, already on a session/server instance.

A example fix for your code, you need to construct the class only during OnBegin like this:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

var SomeStoredObjects : weak_map(session, []some_class) = map{}

GetSomeStoredObjects()<reads>:[]some_class=
    SomeStoredObjects[GetSession()] or array{}

some_class := class:
    block:
        option:
            set SomeStoredObjects[GetSession()] = (SomeStoredObjects[GetSession()] or array{}) + array{Self}

test_weak_maps := class(creative_device):

    OnBegin<override>():void =
        SomeObject := some_class{} # Note that since the class is inside the OnBegin, it is constructed during runtime and not during compile.

Another solution example, you can do something like this, constructing the class at compile, but only storing it to session at runtime:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }

var SomeStoredObjects : weak_map(session, []some_class) = map{}

some_class := class<unique>:
    # No block{} here, we don't want to run stuff such as GetSession() during compile

test_weak_maps := class(creative_device):

    SomeObject : some_class = some_class{} # This line constructs the class but runs during compile

    OnBegin():void=
        Session := GetSession()
        # Required to initialize current session weakmap if not yet initialized
        option:
            not SomeStoredObjects[Session]
            set SomeStoredObjects[Session] = array{}


        # example 1: always add object to weakmap, this should be unfailable
        set SomeStoredObjects[Session] += array{SomeObject} or Err("Unreachable")
        
        # example 2: add object to weakmap only if not exist yet (if class is unique), also should be unfailable
        option:
            not SomeStoredObjects[Session].Find[SomeObject]
            set SomeStoredObjects[Session] += array{SomeObject}

Note that this compile/runtime crash behavior is only for some API calls, such as GetSession (on your case), Scene Queries, GetPlayspace and some other functions… Some will crash and others will silently fail.

Most “offline” APIs such as math, color and other code-only operations/data should still work fine when running during compile (it will be like “hard-coding” that data during compile for that class)

Both of your solutions would force me into hard referencing all those objects somewhere, which is prone to errors and is probably a bad practice, what I want to do is simply implement an Init() method on those objects that would ALWAYS be called when game starts

I want to add that this is not the first time I’m hitting this specific roadblock while trying to achieve a tiny bit complex structures, I’m sure I’m not the only one @Flak

Here’s the fix I made which is the ugliest thing you can ever do, this shouldn’t be how we manage that issue, and I’m pretty sure it would not work on public matches, which means I’d need to also add a layer on top of it to make it work

Being able to access the session state or whatever it’s called (editor/edit mode/play…) at construction time would probably provide a cleaner solution already

Also hopefully SG fixes that and that’s why Epic answer was negative

Thanks for your time everybody

I think you are misunderstanding the concepts of running code in editor, during compile and during gameplay. GetSession() is a API that only works during gameplay, there is no way to have a session if the game is not even running on a server…

What you are trying to do in your current setu (block of a function at class scope) IS hardcodind a data to the class, and since there is no session available at that compile/cook time, it fails.

If you want to init the things during game/when game starts, you should run that logic only inside entrypoints of game loops (OnBegin for creative devices and on initialize/on simulate when scene graph)…

You just need to do the same, but on different place. Can be on a block{}, or can be on a function, just need to run that part of the code only when session is running to be able to retrieve the actual session ID.

Your “workaround” is just a janky crash-avoidance scenario that depends on the ambient where the code runs, and THAT for example, gives problems, it works because:

  • when running during compile, it fails due to no session existing, does not set the map
  • when running on cook, it fails due to no session, it does not set the map
  • when running on a creative server instance, it has session attached to the verse process and sets the map correctly

But, that is a crazy unecessary workaround for something that should be made properly in another way (like my examples before).

And even if it worked and it returned a valid session during compile or cook, it would not be valid, since it would be hardcoded to that testing session, and not the same as the creative match one…

Session is not a magic key/value, it is the server instance that the code is running (in fact, the game shows a uuid session-id on the corners of the game), that is dynamic and changes on every match, not a thing that is always the same to be hardcoded and work fine like you want.

If that’s the case, GetSession() should be failable and should not <computes>

  1. First thing and most important: In verse, Error != Decides/Failable

    • Error in verse is a a raised exception, it means a crash on the VM without possibility to handle it, verse has no error handling or catch operations due to the nature of the language design.
    • <decides>/Failable Expressions are control flow logic (specially built for transactional memory in verse). Think about failable as a syntax sugar for returning true/false if a function succeeded or not (with extra internal logic for transactional memory).

    Don’t get confused, these concepts about failable and error are different and should not be mixed.

  2. <computes> does NOT always succeeds/finishes. It can still cause Errors or run forever (like what is happening on your case). What you mean by never erroring is a “safe” function/API call that never does anything wrong or crashes. That is the <converges> specifier (Check this for more details)


  3. You CAN combine <computes> with <decides>, meaning it CAN fail, the docs also shows it and you can use on your own code if wanted. As I mentioned earlier, being <computes> does not mean that the call is always safe to be made.

  4. The docs also states that “Code that provides default values for class fields or values for global variables is required to have this effect.” about the <converges> specifier, and not the <computes>.
    That does not mean it is prohibited to use other methods that can work even if not always, but just informing that the behavior is not optimal for it due to other scenarios that can happen such as in your usage…
    The code can’t know (at least not yet) if you are building the class instance correctly or not to prevent bad practices and show a warning on these cases (such as inside and outside OnBegin of a creative device or aother class default value).

Your error with your piece of code is still the following: You are trying to hardcode a global value as a class default, where that value is not supported and supposed to be used at that place at that time. (Or at least, should be initialized in a different scenario, like I mentioned on my examples of how to do it properly)

Keep in mind that lots of other APIs also has these same problems if not handled correctly on the expected scenarios, such as constructing a hardcoded material at compile and it not being usable at runtime because of different scenarios (very common problem that I see some people asking help with), and with SpawnProp(), that also used to crash (on this case even the editor) but they changed to just ignore the API call and return false to avoid softlocking editor sessions.