I crave to understand this script

Alright, a while ago I was trying to make a training system that had a timer system. In this case, I was a intermediate scripter/beginner, and while I still am, I wanted to make the attempt to learn the harder systems now. Someone had made my script for the training system, and while he made it very well, I never got to understand what half of this did, so I was just using it without understanding the core of the script, which bothered me a lot. I don’t like using things without understanding the creation of them, so without further ado, here’s the script(s)

	if type(Value) == "table" then
		local NewTable = {}

		for i,v in pairs(Value) do
			NewTable[Copy(i)] = Copy(v)
		end

		return NewTable
	else
		return Value
	end
end

local module = {}

function module:CopyTable(t)
	return Copy(t)
end

-- borrowed, cant stop me dagz >:)
function module:Factory(Class)
	local Object = {}

	for Property,Value in pairs(Class) do
		if Property ~= "new" then
			Object[Property] = Copy(Value)
		end
	end

	return Object
end

local function Connect(Event,Callback)
	local Signal; Signal = {
		Disconnect = function()
			Event.Callbacks[Signal] = nil
		end,
	}
	Event.Callbacks[Signal] = Callback

	return Signal
end

local function Fire(Event,...)
	for _,Callback in pairs(Event.Callbacks) do
		task.defer(Callback,...)
	end
end

function module.NewEvent()
	local Event = {
		Callbacks = {}, -- {Function,...}

		["Connect"] = Connect,
		["Fire"] = Fire,
	}

	return Event
end

return module
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local utility = require(ReplicatedStorage.Modules.Utility)

local module = {}
module.new = function(duration)
	local newTimer = utility:Factory(module)
	
	newTimer.StartTime = tick()
	newTimer.MaxTime = duration or 1
	newTimer.Counting = false
	
	newTimer.Ended = utility.NewEvent()
	
	return newTimer
end

module.Start = function(self)
	if (not self.Counting) then

		self.StartTime = tick()
		self.Counting = true

		task.defer(function()
			while self.Counting do
				if (tick() - self.StartTime) > self.MaxTime then
					self:End()
				end
				task.wait(.1)
			end
		end)
	end
end

module.End = function(self)
	self.Ended:Fire()
	self.Counting = false
end

module.Reset = function(self)
	self.StartTime = tick()
end

return module
1 Like

Your scripter did things in an unconventional way, to say the least. I’ll break it down:

The first script is a utility module. module:CopyTable calls a recursive function that clones a table. module:Factory is a bit strange. It receives a class and creates a new object for that class, even though there are better methods for doing this (I’ll explain later) module.NewEvent() creates a BindableEvent type thing that only exists in scripts.

The second script is the timer script. Here’s where things get a little strange. Instead of doing the standard oop setup (which would totally work in this case!), the author instead uses the Factory function to clone all of the functions of module in the timer script to the new timer. This method works, but it’s an unconventional way of going about something like this. Now, when you create a new timer it has all the functions the rest of the module has (module.Start, module.End, module.Reset)

module.start does exactly what its name implies. It records the time it started counding in self.StartTime. I’m a little annoyed about how the scripter wrote this bit, but they essentially begin a new thread, add a while loop that runs as long as the timer is still counting, and they check if the time has passed the duration. They wait .1 seconds, which I don’t agree with because there will be a variance in the timers duration of up to .1 seconds. In my opinion they should have instead created a new thread that task.waits the duration, and then it calls the Ended function on itself.

If there’s anything you want me to clarify, tell me. I hope you enjoyed my essay!

4 Likes

I’m a very curious person, so I may bombard you with questions about various things. Let’s begin with the first thing I quoted

What he did, is he just making his own working index by cloning the table because he wouldn’t have to go through the work of metatables.

Utility is just a function to create your own object in a scenario like this?

And finally, what is negative about adding a while loop whilst the timer is counting? Was it the part in which you said there will be a variance at times when counting to that specific duration? Also, you would’ve done another coroutine.wrap and do a task.wait to the duration and if it passes the specific time End is Fired automatically?

This makes sense, but working with metatables is not something one should avoid but rather take some time to learn about. Something like this could work:

local timer = {}
timer.__index = timer

function timer.new()
    local newTimer = {}
    setmetatable(newTimer, timer)

    newTimer.startTime = tick()
    -- etc

    return newTimer
end

function timer:start()
    -- code to start the timer
end

Using metatables the scripter no longer needs to use the factory function, and the start function can be called directly on newTimer.

The first script you sent was just a script that was written for utility purposes, with functions that can be used (cloning a table or that factory function for example).

The issue with how it’s currently set up is that it only checks if the timer is already completed every 0.1 seconds. That means if the timer finished (e.g. the condition (tick() - self.StartTime) > self.MaxTime becomes true) right as the task.wait(0.1) begins, the end function will be called up to 0.1 seconds late. It’s not a huge issue, but sometimes you don’t want that variability.

Here’s what I would have done:

task.spawn(function()
    task.wait(self.MaxTime)

    if self.Counting then -- This is only necessary if you ever end the timer before it's done
        self:End()
    end
end)
1 Like

The final question I have is to ask would you consider this an effective timer creation method, or do you believe there would’ve been a superior way to go about it?

There’s really no superior way to go about it; if code works for you and gets the job done, it’s not bad code. The effectiveness of a script is determined by the user (you) and how well it works for them. If you end up needing more functionality from a script, you would make the necessary changes.

That being said, if I were to write a timer system I’d probably want it to be more widely utilized. I’d add a step parameter in the timer.new function that dictates how much time passes between each step in the timer. With that, I’d create something like a bindToTimerStep, where when a certain amount of time passes (step), bound functions would be called with the current time of the timer. Again, for your game, you may not need to make these changes if the purpose of the script is fulfilled and you don’t need to utilize the changes I mentioned.

1 Like