Release Notes for 505

This is one of those things I wish Lua always had. I’m so glad it’s being added!

20 Likes

thank you! :pray::pray::pray:

just had to remove the cursor component from my roact app textboxes because you can’t access user input while textboxes are focused. great timing!

7 Likes

Fixed BillboardGui rendering in VR.

Implemented the new Roblox VR system with the following components :

  • 3rd person and 1st person comfort camera module with vehicle support
  • new in-game menu and player gui interaction
  • support for Oculus Touch and Valve Index controllers

More VR stuff! Although, this is the third(?) time “Support for … Valve Index controllers” has shown up.

12 Likes

For those who care: Vector2/3.zero/one/xAxis/..., CFrame.identity, and cframe.Rotation are live and ready for use.

63 Likes

This new Luau debugger’s API is amazing
Let us use it in our own plugins when it finally ships (or free DebuggerManager)

coroutine.close is also a godsend :pray:

3 Likes

This seems great but how can it be used though?

Can someone provide an example use case?

I’m sure a lot of us are confused and wondering about the same thing

2 Likes

Fun note, with the addition of this function you can now write a pure Lua exit() function:

function exit()
	task.defer(coroutine.close, coroutine.running())
	coroutine.yield()
end
33 Likes

Now to wait for interruptible coroutines, where u can yield running coroutines, with a timeout or from another thread with parallel lua.

1 Like

But still no way to just hard disable VR components from our experiences at all? :confused:

7 Likes

Interruptible coroutines doesn’t make sense, there is only one running coroutine. The running coroutine can’t forcibly yield a normal coroutine (a coroutine which resumed another coroutine, and is still waiting). The running coroutine can obviously yield and return to what resumed it. Yielding with timeout is just one of the waiting functions (task.wait, wait)? Interrupting a coroutine from another thread makes no sense, threads and coroutine are very different concepts (and currently there is no way to share data between threads during unsynchronized execution).


local co = coroutine.create(function()
	while true do
		print(1)
		task.wait()
	end
end)
coroutine.resume(co) -- start printing 1 every frame
task.wait(2)
coroutine.close(co) -- stop printing every frame
-- and then one more error message appears after it stops printing

Is it intended that this generates an error? The error this gives is also not very helpful.

All other task. methods should not generate any error; wait does because it pushes an extra argument to stack before resumption which confuses the scheduler. We have a separate change in the later release that should address this.

4 Likes

@FieryEvent
In parralel lua, I would expect pausing an actor itself, rather than a specific thread (you of course can’t access threads cross actor). I can imagine that actually having some nice use cases.

There is also an alternative interpretation which would work for cooperative threading, though, I doubt it would really be justifiable… The coroutine will just yield again after resumption, e.g. if the coroutine is in the middle of a timed wait, once the wait expires it will remain yielding. This really only makes sense because cases where you are expecting code to start running again on its own are also already the exact cases where you want this, aside from wanting to stop code mid-execution, which, isn’t really feasible in any form.

Assuming it existed, probably would only want this behaviour for yields not explicitly initiated via coroutine.yield (in other words, only non-lua yields, otherwise you get confusing behaviour)

1 Like

VR is an input method, the same applies to controller. If I join your game while using an Xbox controller, but I receive a message it was disabled, I wouldn’t enjoy my experience. The same applies to VR.

1 Like

That’s the point though. I don’t want VR being used in my experience and I don’t want to support VR and its components either. The developer should have a choice on which input methods they want to support (i.e. we already can restrict our experience to certain devices).

I don’t want people using VR in my experience because it looks awful in comparison to playing the intended way (through a computer, handheld device or gamepad) and there’s no support in the codebase for VR either explicitly because it’s not supposed to be played on VR.

You should receive a message that tells you its disabled and that should prompt you to change the way you’re playing to the intended way. What you said is exactly what I want.

3 Likes

Would it be possible to incorporate this with an InvokeClient wrapper and have coroutine.close kill a potentially hung thread if the client overwrites OnClientInvoke to not return anything, or should we still not rely on client invocation and instead use two-way RemoteEvent communication?

I’m not too familiar with coroutines so don’t know how this could be achieved but I just thought about how it could be useful for preventing server hangs with client invocations. I tried mocking something up but not sure if this is the correct way to use it.

local function exitCo()
	task.defer(coroutine.close, coroutine.running())
	coroutine.yield()
end

local function safelyInvokeClient(remoteFunction, player, timeout, ...)
	timeout = if type(timeout) == "number" and timeout >= 0 then timeout else 0
	
	task.delay(timeout, exitCo)
	
	local results = table.pack(remoteFunction:InvokeClient(player, ...))
	
	return table.unpack(results)
end
2 Likes

task.delay(timeout, exitCo)

Alas, that won’t do what you want. delay will just create a new coroutine and the exitCo call will stop that coroutine instead.

You can however write the safelyInvokeClient call that you desire (done here with what I think is the minimal correct code that doesn’t allocate any lambdas so it should be very cheap to use. You could get slightly cheaper if you pooled delay and invocation coroutines to re-use them but then the code would get pretty complicated for not that much benefit):

local function spawnIfNotResumed(resumed, thread, ...)
    if not resumed.value then
        resumed.value = true
        task.spawn(thread, ...)
    end
end
local function invokeAndMaybeResume(resumed, thread, remoteFunction, player, ...)
    spawnIfNotResumed(resumed, thread, true, remoteFunction:InvokeClient(player, ...))
end

-- Returns: true/false, followed by the return values if true (didn't time out)
local function safelyInvokeClient(remoteFunction, player, timeout, ...)
    local thisThread = coroutine.running()
    local resumed = {value = false}
    task.delay(timeout, spawnIfNotResumed, resumed, thisThread, false)
    task.spawn(invokeAndMaybeResume, resumed, thisThread, remoteFunction, player, ...)
    return coroutine.yield()
end
3 Likes

@colbert2677
Personally I’d say that this sort of behaviour is probably good to avoid since it is sort of a thread hack (similar to resuming during a WaitForChild, or resuming during a wait call), and, I’m not really sure I even understand how the behaviour with the remote would work (e.g. with GC and such), that behaviour is made unclear. Not to say this wouldn’t be useful or anything, or that this sort of thing should never be done or will never show up or something.

When it comes to requesting data from the client, best practice is definitely still to write asynchronous code imo.

E.g. the best model to follow imo is that the server will ask the client to report some info, without any expectations that the client will ever actually comply with that request, and, then the client submits a request giving the server an update on the info, and the server does something with that.

It’s usually better to avoid cases where code will act any different because a client didn’t respond, since it leads to some unpredictability, and, can lead to exploits that are super difficult to understand (e.g. exploiters sending responses late, or duplicating responses in the case of events, or not sending responses at all, etc)

I think the best way to think about a client response to a server request is less like a response and more like an action the client is making on its own. The server is just asking the client to perform an action, and leaving it open for whether or not it does can be useful.

1 Like

There’s a few cases where synchronous code is more elegant (though in my experience those cases are always the client calling the server where you wouldn’t need said timeout).

For example, if the user is placing some object this is a very clean pattern:

local tempObject = placeObject(...)
PlaceObjectRemote:InvokeServer(...)
tempObject:Destroy()
5 Likes

Information about coroutine.close() needs to be added to the docs. It should mention how it can be useful.

Also does task.delay(100, co) clean up the moment coroutine.close(co) is called or does it still wait 100 seconds to find out co is already closed?

2 Likes

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.