Made Two ModuleScripts

I’m not the best scripter on ROBLOX, I think we all know that as a fact. But I created two ModuleScripts today that I think will be useful for a lot of developers! And for those of you who are better at scripting than I am, I’d love to learn from you by receiving your input on the source code so that I can improve the scripts further. :slight_smile:

The first ModuleScript is my Script Event Module. It is similar to ROBLOX Instance events, and a sort of sub class called ScriptEventListener. The ScriptEvent class has a couple methods, including :Connect(function f), :Disconnect(function f), and :Fire(…). The ScriptEventListener class has :Disconnect() and :Fire(…). I’m very excited about this one as I’m sure I can use it in a lot of ways!!

The other is my Timer Module. This one is dependent upon the former Script Event Module, but already has that module included as a child of this one so that you don’t have to get both. This one has Timer class that has several methods, such as :Start and :Kill. It also has two ScriptEvents, Changed and Ended. This module allows you to have multiple Timer objects so that you can control things like round duration and the like more easily. :smiley:

Again, those of you whom are better than I am at scripting, please look at the source and let me know of anything you think I could change so as to improve its readability or efficiency. I’d love to learn from you guys. :smiley:

Thank you.

xD Of course ROBLOX goes down as soon as I post about something I made. :stuck_out_tongue:

Just realized a big bug, but I can’t fix it until after ROBLOX’s maintenance, so I’ll do it in the morning. The bug is that the Fire method for the ScriptEvent will run all listener events asynchronously - obviously not correct behavior.

Which of the following methods do you guys suggest I use?

this:Fire(...)
    coroutine.wrap(this.func)(...)
end

or

this:Fire(...)
    local multiArg = ...
    spawn(function() this.func(multiArg) end)
end

I ended up using a coroutine method, as I am familiar and comfortable with those, in addition to a spawn method. I updated both models.

I’ve done something like this before, for my own classes - I’ll definitely look at yours too, though!

Thank you! I plan to use the two ModuleScripts in the coming days and make updates to them as I see fit, but I am definitely looking for help so I can learn. :slight_smile:

[quote] Just realized a big bug, but I can’t fix it until after ROBLOX’s maintenance, so I’ll do it in the morning. The bug is that the Fire method for the ScriptEvent will run all listener events asynchronously - obviously not correct behavior.

Which of the following methods do you guys suggest I use?

this:Fire(...)
    coroutine.wrap(this.func)(...)
end

or

this:Fire(...) local multiArg = ... spawn(function() this.func(multiArg) end) end [/quote]

The two are different. The first one is going to start running each thread immediately, moving on to the next only if the thread yields. This is pretty much exactly how normal events run their listeners. However, any errors that occur at this point will be lost, since you haven’t handled them in any way. Threads that yield via coroutine.yield() will not be resumed, either.

The second one is going to queue each thread without running them right away, which means that just about anything can happen before the functions actually start running. The order in which they run may also not be defined. One advantage is that errors are handled automatically.

So, I’ve taken a look and I don’t have a lot to say about your code. It’s very clean and pleasant to read.

Your event module is set up pretty much exactly the way mine is, except that I have mine split up across two ModuleScripts. I’ll share the source if you want me to.

The timer module kind of seems like a glorified delay() function, but it’s still pretty cool. I like how you used your event module to allow me to check for timer updates, and when the timer was finished running. It would have been cool if the timer had an option to start over when it reaches 0 rather than stopping, but I guess you can script around that with the .Ended event you’ve given it.
Also, your timer would only work on a client, because as far as I’m aware, RenderStepped does not work on an online server.

I also noticed that you used os.time() to detect differences in time - it works, but it doesn’t work well. os.time() only returns full seconds, which means that your timer module and your event:Wait() method both wait at least one second. I suggest that you use tick() instead for these purposes. tick() is extremely precise (on my laptop the minimum time difference is 238.4 nanoseconds), and would allow you to detect much smaller differences in time than os.time() does.

Thank you! :smiley:

Sure, I’d be interested in learning! Would you suggest using two ModuleScripts rather than the one I currently have?

The beauty of the Timer is that I can use it for rounds and other things, and if, say, a team were to achieve the maximum number of points in a round, I could kill the timer. Delay doesn’t give that sort of functionality.

[quote]
Also, your timer would only work on a client, because as far as I’m aware, RenderStepped does not work on an online server. [/quote]
I believe I’m using the Stepped event, not RenderStepped, aren’t I? I actually haven’t tested it on client-side but it works perfectly on server-side.

[quote]
I also noticed that you used os.time() to detect differences in time - it works, but it doesn’t work well. os.time() only returns full seconds, which means that your timer module and your event:Wait() method both wait at least one second. I suggest that you use tick() instead for these purposes. tick() is extremely precise (on my laptop the minimum time difference is 238.4 nanoseconds), and would allow you to detect much smaller differences in time than os.time() does. [/quote]
Oh that’s the method I was looking for the whole time!! I couldn’t find anything more accurate than seconds but all the time I was looking for milliseconds or smaller. Thank you! I’ll tinker that in.

I REALLY appreciate you taking the time to read the code and give your comments on it. Thank you so much!

VolcanoINC, uhm… According to the wiki, tick() returns the number of seconds since the Unix epoch, as well. :confused: Documentation - Roblox Creator Hub

Sure, I’d be interested in learning! Would you suggest using two ModuleScripts rather than the one I currently have?[/quote]
It’s really a question of how you want to organize your code. I personally prefer your way of having just one module rather than my way of using two. I’ll look for the modules and post their source here when I found them.

EDIT: The main module:

[code]Connection = require(script.Parent.EventConnection)

Event = {}
Event.__index = Event

function Event.new()
local new = {}
new.Connections = {}
new.lastfired = 0

setmetatable(new,Event)
return new

end

function Event:connect(func)
local con = Connection.new(func,self)
table.insert(self.Connections,con)
return con
end

function Event:wait()
local called = tick()
while (called > self.lastfired) do wait() end
end

function Event:fire(…)
self.lastfired = tick()

local a
for _,a in pairs(self.Connections) do
	a.func(...)
end

end

return Event
[/code]

The other module:

[code]Connection = {}
Connection.__index = Connection

function Connection.new(func,event)
local new = {}
new.func = func
new.event = event

setmetatable(new,Connection)
return new

end

function Connection:Disconnect()
local i,c
local tbl = self.event.Connections
for i,c in pairs(tbl) do
if (c == self) then
table.remove(tbl,i)
end
end
end

return Connection[/code]

And I just went ahead and put them together into one module:

[code]Connection = {}
Connection.__index = Connection
Event = {}
Event.__index = Event

function Connection.new(func,event)
local new = {}
new.func = func
new.event = event

setmetatable(new,Connection)
return new

end

function Connection:Disconnect()
local i,c
local tbl = self.event.Connections
for i,c in pairs(tbl) do
if (c == self) then
table.remove(tbl,i)
end
end
end

function Event.new()
local new = {}
new.Connections = {}
new.lastfired = 0

setmetatable(new,Event)
return new

end

function Event:connect(func)
local con = Connection.new(func,self)
table.insert(self.Connections,con)
return con
end

function Event:wait()
local called = tick()
while (called > self.lastfired) do wait() end
end

function Event:fire(…)
self.lastfired = tick()

local a
for _,a in pairs(self.Connections) do
	a.func(...)
end

end

return Event[/code]

The beauty of the Timer is that I can use it for rounds and other things, and if, say, a team were to achieve the maximum number of points in a round, I could kill the timer. Delay doesn’t give that sort of functionality.[/quote]
Yeah, I saw that, and I admit that ‘glorified delay() function’ wasn’t the best choice of words.

[quote]
Also, your timer would only work on a client, because as far as I’m aware, RenderStepped does not work on an online server. [/quote]
I believe I’m using the Stepped event, not RenderStepped, aren’t I? I actually haven’t tested it on client-side but it works perfectly on server-side.[/quote]
My bad, I must have misread that. >.<

[quote]
I also noticed that you used os.time() to detect differences in time - it works, but it doesn’t work well. os.time() only returns full seconds, which means that your timer module and your event:Wait() method both wait at least one second. I suggest that you use tick() instead for these purposes. tick() is extremely precise (on my laptop the minimum time difference is 238.4 nanoseconds), and would allow you to detect much smaller differences in time than os.time() does. [/quote]
Oh that’s the method I was looking for the whole time!! I couldn’t find anything more accurate than seconds but all the time I was looking for milliseconds or smaller. Thank you! I’ll tinker that in.

I REALLY appreciate you taking the time to read the code and give your comments on it. Thank you so much![/quote]
You are very welcome! :slight_smile:

I just published big updates. The Timer now has multiple new functions, such as Pause and Resume. Kill is now named Stop. There are several new events, including Started, Paused, and Resumed. Also, all variables are now private and there are getters/setters when appropriate.

I also made some changes to the ScriptEvent module, including separating ScriptEventListener into a sub-ModuleScript. I realize you just changed yours, but I think I prefer having my pseudo-classes organized this way.

A question though - should I be using metatables? I’ve never used them before and honestly don’t really understand them. Help on this topic would be amazing. :slight_smile:

Oh, and thank you for the great idea, VolcanoINC. I also added functionality for it to be able to loop, and essentially restart itself every time “Stop” is called.