I came up with a way to make singletons in Verse

I’ve seen people looking for ways to connect between different devices at a global level, so I came up with a way to make device singletons that work in Verse.

global := class<varies><concrete>(): 
   var MaybeMainDevice : ?main_device = false

GetMainDevice():main_device = 
    if(MainDevice := Global.MaybeMainDevice?) then MainDevice else main_device{}

Global : global = global{}

and the in the device’s OnBegin you call

set Global.MaybeMainDevice = option{Self}

after that you can call GetMainDevice() from anywhere in the rest of code to access it

I’d be curious if anyone knows a better way to do it, but this was the way that worked for me. I’ll break down why I did it this way:

  • You can’t have a mutable variable at module/global scope, so it had to be inside a class (hence the global class).

  • In order to instantiate a class at global scope, it can’t be <transacts>, it has to be <varies> (note: it also can’t be <computes> because of the mutable variable).

  • Because it’s <varies>, we can’t create the “fake” initializer class (main_device{}) directly, instead we have to use an option.

  • We could access the option directly, but then we’d have to always do that in a failure context despite knowing it exists, which is annoying, so instead we create a non-failable GetMainDevice() function to access it

Hope that explains it! There might be some easier way to do this that I missed, so I’d be interested to learn if there are any other approaches to this out there.

8 Likes

Not bad. Personally, I just add custom properties (an @editable) where the variable type is my other device (the actual class).

2 Likes

Good find!

Unfortunately, there is a reason why we don’t let you declare a global var: we’re not done implementing global variables, and want to reserve the right to change their behavior without breaking everybody’s code, so for now we disallow them.

We recently discovered this particular loophole ourselves, and are in the process of closing it. I recommend against using it in the meantime, as once we close the loophole, your code won’t compile.

For now, you only have round-scoped variables on device classes.

4 Likes

Is this because using Verse code in this way allows one to circumvent memory limits? If so, that makes complete sense.

So, having multiple creative_devices reference each other via @editable remains an appropriate means of “inter-script communication”? Assuming yes, since the scripts involved still need to be placed within the world (instanced), just as you normally would. Oh, and, of course, we can’t have a circular dependency (i.e. two scripts depending on each other) - I am pretty sure I’ve gotten that specific error in the past :slight_smile:

Since this loophole is now closed as of 25.0 you probably want to use Gameplay Tags instead.

For now using tags is probably the best way to programmatically gather one or more devices.

We are discussing this internally at Epic and more related mechanisms will come in the future.

6 Likes

With weakmaps implemented is this now viable again?

I make a main device and have other custom devices (or arrays of custom devices) be @editable to the main device. This way it’s easy to get devices communicating with each other. It also can ensure that devices initialize as they should.

1 Like