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.
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.
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.
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
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.
[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.
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!
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.
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:
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!
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.
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.