Signal that is invoked when a certain SetCore/GetCore item is registered (GetCoreRegisteredSignal)

As a developer, it is difficult to use core items via GetCore/SetCore in a clean way. This is because you have to a wait-pcall loop for the core item to be available.

Problem

This is a safe way to set ResetButtonCallback, instead of directly calling SetCore outside of a loop without pcall wrapper:

for _ = 1, ATTEMPTS do -- or "while true do" (less safe)
	if pcall(function() StarterGui:SetCore("ResetButtonCallback", bindable) end) then
		break
	end
	Stepped:Wait()
end

The downsides of this structure are:

  • I have to write a similar nasty block for each core item that I want to change via SetCore.
  • This code looks horrible for the simple task it performs, it is hard to understand and read.
  • Yielding is the root of all evil (after micro-optimization).
  • I have to keep throwing calls at SetCore every frame until it eventually passes, which is somewhat silly. It wastes cycles (in principle, not that it actually matters) and is a mild case of busy-waiting (only grace is that I know it only needs to check once per frame, and I have no API to wait for a shorter amount of time).
    • This case would be improved if we had a way to find out if a core item is registered, such as StarterGui:IsCoreRegistered(name).
  • I don’t know at what timing the core items are registered, so it is ambiguous whether it is best to use Stepped / RenderStepped / Heartbeat / etc to get the earliest timing possible to get/set the core item value.
  • If the core item ever stops existing in some future update (which can happen, since registrations are not guaranteed), my code is busy looping over many frames checking the core item for no reason, since it will never be registered and so my loop will never break / never accomplish anything. This unnecessarily delays other code in my game. Even having StarterGui:IsCoreRegistered(name) (see above) does not solve this.
  • The design of the API makes it easy to make mistakes: due to how difficult the code block above is to understand, it’s hard to explain to developers how they should properly wait for core items to be available.
    • They might do the call without pcall/loop because the correct way to do it might be obscured / confusing, and then, if it happens to work by chance, they might leave that code be and it will break their game once the timing changes / the core registration is removed entirely. (See for actual example of this: TopbarEnabled broken)

Request

It would be convenient if there was a way for developers to set a callback for any given string, that is called once, as early as possible, when the corresponding core item with that name is registered.

Example: (in this sample, I assume it would be StarterGui::GetCoreRegisteredSignal)

StarterGui:GetCoreRegisteredSignal("ResetButtonCallback"):Connect(
   function()
      StarterGui:SetCore("ResetButtonCallback", bindable)
      -- whatever other logic needs to happen
   end
)

This way, the code to safely set/get a core item is much cleaner, easier to read and understand, and does not require constant looping and calling of SetCore. If the registration ever stops existing, then all I lose is a little bit of memory keeping the connection and function in memory – I won’t have to create a separate Lua thread to make sure none of my other code is delayed and the function will just not run if the core registration is never registered.

Finally, if the call is guaranteed to be made as soon as the core registration is made (i.e. either exactly after registration, or as soon as possible the frame after), it eliminates any timing issues that causes things to flicker between two states, for example when using the ChatWindowSize or ChatWindowPosition core registrations to move the chat elsewhere but not invoking SetCore as soon as the chat registers its own core items.


If Roblox is able to address this request, interactions with SetCore and GetCore would become less hacky and much more convenient.

In addition to considering adding these signals, I highly recommend considering the feature request linked earlier as well, as it is related to some of the issues posed in this topic and might cover some alternative approaches to implementing cleaner behavior for using core registrations:
https://devforum.roblox.com/t/methods-to-wait-for-corescript-registry/36609

10 Likes

What happens when the signal is connected after the core value has been registered? It can’t fire immediately upon connecting, because that’s absolutely broken. It would need a companion IsGetCoreRegistered function in order to be effective. Also remember that there’s both GetCore and SetCore. There would have to be a set of members for both (“GetGetCoreRegisteredSignal” is rather awkward).

Coming up with the right API requires looking at how core values are used. Are they set once and forgotten? Are they modified dynamically? Is Get/SetCore expected to fail or yield?

Personally, I like preregistration, described in the linked post as Solution C. It puts zero burden on developers (no changes required), and the worst-case scenario is that a value isn’t preregistered, which falls back to the current behavior. For engineers, the only thing that changes is that a list of core values is preregistered somewhere early, like the StarterScript.

2 Likes