I’m trying to create a script that loops through a folder, gets an instances path and then uses that path to find an instance in a different folder. I’m currently using Instance:GetFullName()
to get the instance’s path, however I do not know how to use this to find the instance in the folder in my script. Help greatly appreciated, thank you!
I, for whatever, found myself having a hard time trying to give a generic answer. The truth is, it’s implementation defined, i.e., I’d need to know what the parent-child hierarchy looks like, and what might be the best solution.
To be real, there are three steps involved:
- Obtain a relative path, as I’d like to call it. (basically a path that is valid when appended to the folder’s)
- Append that relative path to the folder’s.
- Canonicalize the result, i.e., convert the
string
path into anInstance
.
Depending on the implementation, we could skip steps 2 and 3 altogether, by doing them all at the same time (in some way or another).
Here’s an example implementation:
local function relativeTo(instance: Instance, path: {string}): Instance
for _, name in ipairs(path) do
instance = instance[name]
end
return instance
end
local function cutEnds(path: string, at: number): {string}
local path = path:split(".")
local final = {}
for i = #path - at + 1, #path do
table.insert(final, path[i])
end
return final
end
local absolute = workspace["MyFolder"]--["MyModules"]["Module"]
local relative = workspace["OtherThing"]["MyModules"]["Module"]
relativeTo(absolute, cutEnds(relative:GetFullName(), 2))
--> ["MyFolder"]["MyModules"]["Module"]
I plan to loop through a folder and I need to find every script in this folder. The parent-child hierarchy is different for each instance I need to replace. I then need to replace this script with the script from my folder.
Below is my interpertation of this code and some questions.
What does this code do?
absolute is the new folder that the path from the old folder will be used on.
relative is the path from the old folder to be used on the new folder (absolute)
The cutEnds() function loops through the path provided and converts it to a table, with each new entry being an instance in the path.
the relativeTo() function loops through the new path converted to a table and converts it to a string, with each new path in the string coming after the previous.
Implementation:
for i,v in workspace.MyFolder:GetDescendants() do
if v:IsA("ModuleScript") then
local absolute = workspace.MyFolder
local relative = v
local path = relativeTo(absolute, cutEnds(relative:GetFullName(), 2))
if script.ClientSidedObjects[path] then
script.ClientSidedObjects[v.Parent.Name][v.Name]:Clone().Parent = v.Parent
v:Remove()
end
end
end
Questions:
Does this implementation look correct to you?
Why is there a 2 at the end of the function? I couldn’t find where it is used in the function.
Was my interpertation + interpertation of this correct?
Sorry for the wall of text and bombardment of questions.
Off-topic, but your awesome-roblox repo on your GitHub, is incredibly useful.
The cutEnds
function returns the last n
elements of a path, separated by a dot. E.g., cutEnds("hello.world.me.you", 2)
would return { "me", "you" }
. If the parameter was 3
, it would return { "world", "me", "you" }
.
In order to extract "MyModules"
and "Module"
from the relative path, I used two, since they were the last two elements separated by a dot.
Oh, and by the way, I edited my previous reply because there were some minor bugs. Should work now, in theory.
That makes it a tad bit more difficult. This is why I said it was “implementation defined”. If I understood correctly, the hierarchy will have a variable hierarchy depth. This means using 2
as a constant will be no good.
What we have to do is adapt the previous solution. Since we know for sure that any child of folder 1
is guaranteed to exist in folder 2
:
local function relativeTo(
to: Instance,
ancestor: Instance,
nestedInstance: Instance
)
-- just for debugging, remove later
--assert(ancestor:IsAncestorOf(nestedInstance))
local function cutEnds(path: {string}, at: number): {string}
local final = {}
for i = #path - at + 1, #path do
table.insert(final, path[i])
end
return final
end
local function relativeTo(instance: Instance, path: {string}): Instance
for _, name in ipairs(path) do
instance = instance[name]
end
return instance
end
local nestedPath = nestedInstance:GetFullName():split(".")
local ancestorPath = ancestor:GetFullName():split(".")
local relativePathSize = #nestedPath - #ancestorPath
local relativePath = cutEnds(nestedPath, relativePathSize)
return relativeTo(to, relativePath)
end
In this case, the function will calculate the depth beforehand, as long as we are aware of ancestor
(which is a given).
I genuinely don’t know why I didn’t do this earlier. My bad.
You can even use an adapted version on the Luau demo. Feel free to ignore the code below.
local function print_tbl(tbl)
for k, v in pairs(tbl) do
print(`['{k}'] = '{v}'`);
end
end
local function relativeTo(
to: Instance,
ancestor: Instance,
nestedInstance: Instance
)
-- just for debugging, remove later
--assert(ancestor:IsAncestorOf(nestedInstance))
local function cutEnds(path: {string}, at: number): {string}
local final = {}
for i = #path - at + 1, #path do
table.insert(final, path[i])
end
return final
end
local function relativeTo(instance: Instance, path: {string}): Instance
for _, name in ipairs(path) do
instance = instance[name]
end
return instance
end
-- path is hardcoded because we can't use :GetFullName
local nestedPath = ("workspace.OtherThing.MyModules.Module"):split(".")
local ancestorPath = ("workspace.OtherThing"):split(".")
local relativePathSize = #nestedPath - #ancestorPath
local relativePath = cutEnds(nestedPath, relativePathSize)
return relativeTo(to, relativePath)
end
local workspace = {
MyFolder = {
MyModules = {
Module = {
Name = "bababooey"
}
}
};
OtherThing = {
MyModules = {
Module = {
Name = ">:)"
}
}
}
}
local to = workspace["MyFolder"]--["MyModules"]["Module"]
local ancestor = workspace["OtherThing"]
local relative = workspace["OtherThing"]["MyModules"]["Module"]
print_tbl(relativeTo(to, ancestor, relative))
--> ['Name'] = 'bababooey'
I tried recreating my own script so I really master this and learn from this. I am currently stuck because in the FindPath()
function I cannot get folder to return as a list of its parents until the chosen folder and it instead returns as a single instance, which causes the script.ClientSidedObjects[path].Parent = path.Parent
line to error. Do you know how to make it return differently? I didn’t see you do something different in your code and was also curious how yours works, but mine errors.
function FindPath(
instance, -- modulescript to update
folder) -- folder to loop through + folder to parent new COs to (gets updated COs from script.ClientSidedObjects)
local path = tostring(instance:GetFullName()):split(".")
print(path)
for i,v in pairs(path) do
if i <= 3 then -- dont loop through if i is 2 or less because "Tower" and "Model" and "ClientSidedObjects" are not parts that are wanted in the path
folder = folder[v]
end
end
return folder
end
function UpdateScript(instance, folder)
local path = FindPath(instance, folder)
script.ClientSidedObjects[path].Parent = path.Parent
instance:Remove()
end
There’s no need for tostring
on GetFullName
, because Instance:GetFullName()
already returns a string
.
I think you confused the operator here. <=
means lower than or equal to, meaning the if statement would only execute if i
was equal to or lower than 3, which is the opposite of your intended logic.
Another thing: because you’re iterating using pairs
(which should’ve been ipairs
, really), the loop will start at index 1, meaning it would return any element after index 4 (since anything below <= 3 is ignored), not the last n elements.
You tried to index an Instance
with an Instance
. I think you meant to do path.Name
. Not only that, this would only work is path
was a direct child of ClientSidedObjects
. If it were to be a descendant (indirect child), it wouldn’t be able to find a result.
This is why I created a helper function which would be able to index descendants even (the relativeTo
helper function, which is inside relativeTo
(nested functions / local functions))
Instance:Remove()
has been deprecated in favor of Instance:Destroy()
, so use that instead.
The problem I am having is that FindPath(instance, folder)
does not return the path, only the instance. In your nested relativeTo()
function, all that I can see is that it would return the instance as well and not the path. Am I incorrect? How would I get the path and use it?
What path are you looking for? Is it relativePath
maybe? At the end of the day, you could call GetFullName
on the result of the main relativeTo
function, no? Or did I misunderstand you? (likely the case)
Yes, I am looking for the relativePath. To better explain, the path only from the child to the folder that is the same between the folder of updated ModuleScripts and the folder with unupdated ModuleScripts.
It’s already defined in the code. What you’d want to do is add this as a second return value to the main relativeTo
, so it’d look like this:
...
return relativeTo(to, relativePath), relativePath
And so:
local instance, path = relativeTo(to, ancestor, descendant)
print("instance:", instance.Name)
print("path:", path)
Code works! What should I do about instances with the same name?
Solution: don’t use the same names
Just kidding.
Not really.
I mean, I don’t think there’s any way to avoid name collisions. It’s impossible to differentiate between two instances with the same name, unless you filter them out yourself (such as by ClassName
), but the implementation would look overly complicated (you’d replace the indexing operator []
with a for loop).
I fixed it! I added this to the nested relativeTo()
function:
local function relativeTo(instance: Instance, path: {string}): Instance
for _, name in ipairs(path) do
if instance:FindFirstChild(name) then
instance = instance:FindFirstChild(name)
end
end
return instance
end
Also, how do I use the “path” output to refer to the instance in the script. I am currently doing this, but it’s not working. I believe part of this is is because path is a table and I don’t think I’m using the right syntax to refer to a path of instances either.
local instance, path = relativeTo(script.ClientSidedObjects, Tower.Tower.ClientSidedObjects, v)
script.ClientSidedObjects[path].Parent = instance.Parent
instance:Destroy()
Currently, it has no significant difference. Some might argue it is worse because FindFirstChild
is slower than []
, and they it’s not like they differ much in behavior (i.e., they would return the same instance if a collision were to occur) (AFAIK)
That’s right. Instead of using path
directly, you would have to manually index each name. In fact, that’s exactly what the nested relativeTo
function does.
Oh, and one more thing, since it seems you misunderstood this part: relativeTo
(the main one) already does the whole job of indexing the relative path for you. You don’t need to do script.ClientSidedObjects[path]
because instance
is already the one existing under script.ClientSidedObjects
. That’s why you provided it as the first argument.
Also, I’m pretty sure this eliminates the need to keep track of the path
variable. I assumed you wanted it for some other purpose, but since that isn’t the case, keep it the way it was.
How would I manually index each name in path since the distance from the folder varies?
It should already account for such cases, since the path’s size is automatically calculated.
To update the script would I just do:
path.Parent = instance.Parent
instance:Destroy()
Why would you update it all? Remember that the relativeTo(to, ancestor, descendant)
function simply returns an instance that is a descendant of to
by using descendant
’s path as a reference.
Not this script, I need this function to loop through a folder and replace all the ModuleScripts in it.
for _, module in Tower.Tower.ClientSidedObject:GetDescendants() do
if module:IsA("ModuleScript") then
local otherModule = relativeTo(
script.ClientSidedObjects,
Tower.Tower.ClientSidedObject,
module)
local parent = module.Parent
module:Destroy()
otherModule.Parent = parent
end
end
Something kinda like this