Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
1

Question about variables being defined in events and used in different scopes?

Asked by 1 year ago
Edited 1 year ago

Okay so Let's say I had code like this:

game.Workspace.ChildAdded:Connect(function(added)
    -- some code
end)

added.Transparency = 1* -- if I tried to run this it would say (unknown global "added") or something

I'm not asking about that code in specific I'm more of asking if I wanted for whatever reason to use "added" which was defined in the event in a different scope, how would I do that if possible?

also do variables defined in that line like "added" have an actual name for them or is it just variable.

Let me know if that's hard to understand, and thank you!

1 answer

Log in to vote
2
Answered by 1 year ago
Edited 1 year ago

Solution (don't do this)

If you want to access the parameter values of callback functions outside of the callback function (which is what you're doing in your example code), you can simply declare a local variable outside of the callback and assign it to whatever the function argument is within the callback:

local added

workspace.ChildAdded:Connect(function(_added)
    added = _added
end)

However, there is a big problem with using this as a solution to your problem and you should not do it this way.

Why shouldn't you do this?

1) Flow

The flow of your program is incompatible with a solution like this because events execute code in their own threads which are asynchronous and separate from the main thread. Other threads will only have a chance to execute once the main thread is done executing, or while the main thread is yielding (waiting) for other threads to finish executing. You can think of a thread as a chunk of code that is scheduled to run some time in the future, instead of immediately. In other words, the time of execution for any given thread is unknown at runtime.

This is a problem because our main thread is executing top to bottom, and if we have some code near the bottom that relies on some code near the top (and it's running in another thread), we cannot guarantee that we will have that data by the time we need it.

For example, in your sample code you're using the ChildAdded event. This means the handler function for that event won't execute until a new instance is added to the workspace, which means we will not have access to that instance until it is added.

Ex. 1: So, consider the ramifications of the solution:

-- declare local variable
local added

-- create event listener (a new thread)
workspace.ChildAdded:Connect(function(_added)
     -- assign local variable
    added = _added
end)

-- use local variable
print(added)

The event listener thread won't have a chance to execute until the main thread has finished executing, or until we manually wait for a response from the ChildAdded event. Therefore, the added variable will never be assigned by the time we try to print it to the output console.

One solution to this could be to wait for the event to fire before executing the rest of the main thread, like this:

Ex. 2:

local added

workspace.ChildAdded:Connect(function(_added)
    added = _added
end)

-- hault main thread execution until a ChildAdded event fires
workspace.ChildAdded:Wait()

print(added) -- now it prints the instance

But this is at best, only a conditionally good solution. Most of the time haulting your main thread execution while waiting for some kind of response or event in your program yields a bad user experience due to continously waiting for things to happen. This is called code blocking, or synchronous code.

2) Namespaces

This solution will also clutter namespaces. You should aim to minimize the amount of globally-scoped variables in your code as much as possible. This isn't as big of an issue as the first one mentioned, because you can fix this by creating your own namespaces with tables or do-end blocks for local variables. However, this will only result in cluttering your program with new tables and scopes which leaves you with the same problem as before.

For example, you could create a table which stores variables whos values are generated from event callbacks, like such:

local eventVariables = {}

workspace.ChildAdded:Connect(function(_added)
    eventVariables.added = added
end)

game.Players.PlayerAdded:Connect(function(newestPlayer)
    eventVariables.newestPlayer = newestPlayer
end)

-- first prints nil
print(eventVariables.added) 
print(eventVariables.newestPlayer)

-- wait for event handler threads
workspace.ChildAdded:Wait() 
game.Players.PlayerAdded:Wait()

-- now print the instances
print(eventVariables.added) 
print(eventVariables.newestPlayer)

Note that this is just a more organized version of the same solution in Flow-Ex. 2 by getting rid of the namespace problem, but this is still just as bad as the original solution.

The good solution

Finally, the good solution is just to use functions to pass around data to wherever you deem necessary. You can think of a function as a passageway, or a tunnel between threads and other code execution contexts for communicating.

If you need to use added outside of your event handler, then create a function that reads or mutates that value in some way and pass it to that function as an argument.

For example:

local function printAdded(added)
    print(added)
end

workspace.ChildAdded:Connect(function(added)
    printAdded(added)
end)

This may seem like a redundant example since you could also just do the following:

local function printAdded(added)
    print(added)
end

workspace.ChildAdded:Connect(printAdded)

However, the first example implies that you can create multiple functions of which you can pass the same argument to, if you need to use the variable in different places for different reasons.

For example:

local function printAdded(added)
    print(added)
end

local function renameAdded(added, newName)
    added.Name = newName
end

workspace.ChildAdded:Connect(function(added)
    printAdded(added)
    renameAdded(added, "NewName")
    printAdded(added)
end)

Now, whenever a new instance is added to the workspace you will get the following output:

(Original Name)

"NewName"

Let me know if this helped. If you have any questions, feel free to ask.

0
Thank you so much for going so in-depth with answering this, I really appreciate this asmetics 26 — 1y
Ad

Answer this question