RoStrap / Resources

RoStrap's Core Bootstrapper

Home Page:https://rostrap.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Can the initial Resource Moving be optimized further?

Validark opened this issue · comments

This is the code in question. I've considered coroutines, doing two repeat until loops, one for the stuff that should go to ReplicatedStorage and one for the stuff that shouldn't.

Coroutines slowed down my speeds in my last test, because it involves a lot of function calls, and currently it only uses the GetChildren() function (which is written in C++), aside from one or two GetFolder and GetLocalFolder calls for the top folder the Modules need to be parented to. The coroutine solution is much slower because it involves way more function calls.

The current implementation minimizes the number of checks that need to be done for each object, but is there any way to remove these checks entirely? The aforementioned method of two repeat until loops could work. I haven't explored it extensively but preliminary tests have yielded little change. See next comment for more details.

local Repository, ServerRepository, ServerStuff -- Repository folders
local Boundaries = {} -- This is a system for keeping track of which items should be stored in ServerStorage (vs ReplicatedStorage)
local Count, BoundaryCount = 0, 0
local NumDescendants, CurrentBoundary = 1, 1
local LowerBoundary, SetsEnabled
Modules = {ModuleRepository}

repeat -- Most efficient way of iterating over every descendant of the Module Repository
	Count = Count + 1
	local Child = Modules[Count]
	local Name = Child.Name
	local ClassName = Child.ClassName
	local GrandChildren = Child:GetChildren()
	local NumGrandChildren = #GrandChildren

	if SetsEnabled then
		if not LowerBoundary and Count > Boundaries[CurrentBoundary] then
			LowerBoundary = true
		elseif LowerBoundary and Count > Boundaries[CurrentBoundary + 1] then
			CurrentBoundary = CurrentBoundary + 2
			local Boundary = Boundaries[CurrentBoundary]

			if Boundary then
				LowerBoundary = Count > Boundary
			else
				SetsEnabled = false
				LowerBoundary = false
			end
		end
	end

	local Server = LowerBoundary or Name:lower():find("server")

	if NumGrandChildren ~= 0 then
		if Server then
			SetsEnabled = true
			Boundaries[BoundaryCount + 1] = NumDescendants
			BoundaryCount = BoundaryCount + 2
			Boundaries[BoundaryCount] = NumDescendants + NumGrandChildren
		end

		for a = 1, NumGrandChildren do
			Modules[NumDescendants + a] = GrandChildren[a]
		end
		NumDescendants = NumDescendants + NumGrandChildren
	end

	if ClassName == "ModuleScript" then
		if Server then
			Modules[Name] = Child
			if not ServerRepository then
				ServerRepository = GetLocalFolder("Modules")
			end
			Child.Parent = ServerRepository
		else
			if not Repository then
				Repository = GetFolder("Modules")
			end
			Child.Parent = Repository
			if not Modules[Name] then
				Modules[Name] = Child
			end
		end
	elseif ClassName ~= "Folder" and Child.Parent.ClassName == "Folder" then
		if not ServerStuff then
			ServerStuff = GetLocalFolder("Server", ServerScriptService)
		end
		Child.Parent = ServerStuff
	end
	Modules[Count] = nil
until Count == NumDescendants
ModuleRepository:Destroy()

Testing two repeat until loops:

local Repository, ServerRepository, ServerStuff -- Repository folders
local Count, NumDescendants = 0, 1
Modules = {ModuleRepository}
local ServerThings = {}
local ServerThingsCount = 0

repeat -- Most efficient way of iterating over every descendant of the Module Repository
	Count = Count + 1
	local Child = Modules[Count]
	local Name = Child.Name
	local ClassName = Child.ClassName
	local GrandChildren = Child:GetChildren()
	local NumGrandChildren = #GrandChildren

	if NumGrandChildren ~= 0 then
		for a = 1, NumGrandChildren do
			Modules[NumDescendants + a] = GrandChildren[a]
		end
		NumDescendants = NumDescendants + NumGrandChildren
	end

	local Server = Name:lower():find("server")

	if Server then
		ServerThingsCount = ServerThingsCount + 1
		ServerThings[ServerThingsCount] = Child
	else
		if ClassName == "ModuleScript" then
			if not Repository then
				Repository = GetFolder("Modules")
			end
			Child.Parent = Repository
			if not Modules[Name] then
				Modules[Name] = Child
			end
		elseif ClassName ~= "Folder" and Child.Parent.ClassName == "Folder" then
			if not ServerStuff then
				ServerStuff = GetLocalFolder("Server", ServerScriptService)
			end
			Child.Parent = ServerStuff
		end
	end				
	Modules[Count] = nil
until Count == NumDescendants
Count = 0
repeat
	Count = Count + 1
	local Child = ServerThings[Count]
	local Name = Child.Name
	local ClassName = Child.ClassName
	local GrandChildren = Child:GetChildren()
	local NumGrandChildren = #GrandChildren

	if NumGrandChildren ~= 0 then
		for a = 1, NumGrandChildren do
			ServerThings[ServerThingsCount + a] = GrandChildren[a]
		end
		ServerThingsCount = ServerThingsCount + NumGrandChildren
	end

	if ClassName == "ModuleScript" then
		Modules[Name] = Child
		if not ServerRepository then
			ServerRepository = GetLocalFolder("Modules")
		end
		Child.Parent = ServerRepository
	end
until Count == ServerThingsCount
ModuleRepository:Destroy()

Times:
Original

0.0012824535369873
0.0013079643249512
0.0011956691741943
0.0013306140899658
0.0015277862548828

Modified, two repeat untils

0.0023126602172852
0.0012509822845459
0.0012736320495605
0.0013413429260254
0.00126051902771
0.0012862682342529
0.0013382434844971

This is just an initial test. I will work more on this another day, but I'm kind of tired and unable to see areas for improvement. I am just posting this to keep a record and to receive suggestions if anyone has any.

Will probably need to create a testing workbench that can repeat the task a few 100 times to best see results once the best version of the modifications suggested have been best implemented

@Narrev should we consider allowing the user to have the modules in ReplicatedStorage from the get-go?

The thought has crossed my mind, we could just completely remove the movement functionality

Pros:

  • The user would have complete understanding and control of which Modules are replicated and which aren't

Cons:

  • It would take away from the whole "unified codebase" thing
  • The user would need to navigate two repositories for their game instead of one

We should note that Modules on Github should have some kind of Replicatable tag for whether it is server-side or not (for installing modules with the plug-in). It should then be placed in either the ServerStorage repository or ReplicatedStorage repository accordingly, if we separate the codebase.

Hi. This is already closed, but I want to make a note.

As the saying goes, "Premature optimization is the root of all evil"

Please note that 0.001 seconds is a very small time for something that happens once and is bootstrapping the whole game.

I would highly prioritize readability and reliability over performance in the bootstrapper. Keeping it simple is much more important than this minor optimization. Nevermore's old bootstrapper as of now is simply to allow lookup of resources by name instead of by path, that is, allow code to be reorganized easily without having issues with submodules.

My two cents.

I can agree with @Quenty here. Further optimization cannot reward enough performance to compensate the fact that the code base becomes increasingly more difficult to read, understand and edit.

I wasn't going for 0.001 seconds here. In fact, I was hoping to explore the possibility of halving setup time. Because the changes I made had a negligible impact (noise had more impact than the changes), I closed this issue because I concluded that we are already loading resources as fast as possible until Roblox adds a GetDescendants function.

This is not premature optimization, this is optimizing the set-up code of a 6-year-old plus bootstrapper that entire games run on.

My bootstrapper works 100% the way I want it to, every single time. It is completely reliable, and if you have a problem with it, we have a dedicated issues tab at the top of this page.

I have decided to leave the Resource initialization the way it is. I don't think it is too complicated or unreadable, and if you have either of those issues, you can keep your version however you want it. I would also point out that this module is not part of "the codebase". It is what organizes and runs the codebase, and does not need to be understood by every individual that uses it. All users need to know is the API.

I've experimented with various solutions to handling RobloxLua's inability to yield during metamethod functions (LuaJIT solved this problem), and one of the bad solutions was to not yield for the top-level folders, and replace functions with smart tables. This did not work for devSparkle. Why?

devSparkle has given me the perspective that Roblox servers are slow. Sometimes really slow. Because when clients used to not yield for the initial folder creation from the server, he would report to me that every so often someone's Nevermore client would break because the folder creation hadn't occurred yet on the server, or at least hadn't been replicated yet.

We have since switched over to having the client yield for every resource (for the very reason aforementioned), but the point is that having sluggish code where it counts isn't some kind of virtue. The module-moving code is a derivative of Utility.CallOnDescendants if you don't understand it.

I am certainly not going to change Utility.CallOnDescendants or the current module-moving-setup-code just so you can feel like you can read it easier.

If you have any questions concerning how it works, I would be happy to go through how the code works with you. In fact, I think I might release a YouTube video just for you two explaining line by line, why each choice was made and exactly what happens where for every case.

This module can do everything your old one can do and much more, all the while doing it faster and with arguably more intelligent error messages. If you can make it faster, submit a pull request. If you have an issue or feature request, I would be more than happy to write a fix or reasonable feature for this module that I constantly maintain.

Otherwise, you are just criticizing a job well-done, and I don't want to hear it.

@Narrev while I agree with you here, I'd just like to correct that what I meant by readability was that we should maintain a certain degree as to allow for quick onboarding for other contributors to the framework.

@devSparkle If you have a specific suggestion or complaint, I can address it. Otherwise, all I can respond with is:

I'll keep that in mind.