diff --git a/BSD-LICENSE.txt b/BSD-LICENSE.txt new file mode 100644 index 0000000..f34a477 --- /dev/null +++ b/BSD-LICENSE.txt @@ -0,0 +1,26 @@ +Copyright (c) 2011, Enrique García Cota +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. Neither the name of MiddleClass nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/README.textile b/README.textile new file mode 100644 index 0000000..476131f --- /dev/null +++ b/README.textile @@ -0,0 +1,40 @@ +h1. cron.lua + +@cron.lua@ are a set of functions for executing actions at a certain time interval. + +h1. Examples of use + +* @cron.after(time, f)@ will execute f after the given amount of time units. +* @cron.every(time, f)@ will repeat the same action periodically. +* @cron.cancel(id)@ will stop a timed event from happening. @id@ is returned by @cron.after@ and @cron.every@ respectively. It will stop a timed event from happening, or stop a periodic action. +* @cron.reset()@ removes all timed and periodic actions, and resets the time passed back to 0. +* @cron.update(dt)@ is needed to be executed on the main program loop. @dt@ is the amount of time that has passed since the last iteration. When @cron.update@ is executed, cron will check the list of pending actions and execute them if needed. + + +h1. Gotchas / Warnings + +* @cron.lua@ does *not* implement any hardware or software clock; you will have to provide it with the access to the hardware timers, in the form of periodic calls to @cron.update@ +* @cron@ does not have any defined time units (seconds, milliseconds, etc). You define the units it uses by passing it a @dt@ on @cron.update@. If @dt@ is in seconds, then @cron@ will work in seconds. If @dt@ is in milliseconds, then @cron@ will work in milliseconds. + +h1. Installation + +Just copy the cron.lua file somewhere in your projects (maybe inside a /lib/ folder) and require it accordingly. + +Remember to store the value returned by require somewhere! (I suggest a local variable named @cron@) + +
+local cron = require 'cron'
+
+ +Also, make sure to read the license file; the text of that license file must appear somewhere in your projects' files. + +h1. Specs + +This project uses "telescope":https://github.com/norman/telescope for its specs. If you want to run the specs, you will have to install telescope first. Then just enter the spec folder and execute run.lua: + +
+cd path/to/cron.lua/specs
+lua run.lua
+
+ + diff --git a/cron.lua b/cron.lua new file mode 100644 index 0000000..7cb5382 --- /dev/null +++ b/cron.lua @@ -0,0 +1,60 @@ +----------------------------------------------------------------------------------------------------------------------- +-- cron.lua - v1.0 (2011-04) +-- Enrique García Cota - enrique.garcia.cota [AT] gmail [DOT] com +-- time-related functions for Lua. +-- inspired by Javascript's setTimeout and setInterval +----------------------------------------------------------------------------------------------------------------------- + + +local entries = {} + +local TimedEntry = {} + +function TimedEntry:new(time, callback, ...) + return setmetatable( { + time = time, + callback = callback, + args = {...}, + running = 0 + }, + { __index = TimedEntry } + ) +end + +function TimedEntry:update(dt) + self.running = self.running + dt + if self.running >= self.time then + self.callback(unpack(self.args)) + self.expired = true + end +end + +local cron = {} + +function cron.reset() + entries = {} +end + +function cron.after(time, callback, ...) + assert(type(time) == "number" and time > 0, "time must be a positive number") + assert(type(callback) == "function", "callback must be a function") + + local entry = TimedEntry:new(time, callback, ...) + entries[entry] = entry +end + +function cron.update(dt) + assert(type(dt) == "number" and dt > 0, "dt must be a positive number") + + local expired = {} + + for _, entry in pairs(entries) do + entry:update(dt, runningTime) + if entry.expired then expired[entry] = entry end + end + + for _, entry in pairs(expired) do entries[entry] = nil end +end + +return cron + diff --git a/spec/cron_spec.lua b/spec/cron_spec.lua index 731941f..cea5e6f 100644 --- a/spec/cron_spec.lua +++ b/spec/cron_spec.lua @@ -2,4 +2,65 @@ local cron = require 'cron' context( 'cron', function() + local counter = 0 + local function count(amount) + amount = amount or 1 + counter = counter + amount + end + + before(function() + counter = 0 + cron.reset() + end) + + context('update', function() + test( 'Should throw an error if dt is not a positive number', function() + assert_error(function() cron.update() end) + assert_error(function() cron.update(-1) end) + assert_not_error(function() cron.update(1) end) + end) + end) + + context('reset', function() + test('Should cancel all actions', function() + cron.after(1, count) + cron.after(2, count) + cron.update(1) + assert_equal(counter, 1) + cron.reset() + cron.update(1) + assert_equal(counter, 1) + end) + end) + + context( 'after', function() + test( 'Should throw error if time is not a positive number, or f is not function', function() + assert_error(function() cron.after('error', count) end) + assert_error(function() cron.after(2, 'error') end) + assert_error(function() cron.after(-2, count) end) + assert_not_error(function() cron.after(2, count) end) + end) + + test( 'Should execute timed actions are executed only once, at the right time', function() + cron.after(2, count) + cron.after(4, count) + cron.update(1) + assert_equal(counter, 0) + cron.update(1) + assert_equal(counter, 1) + cron.update(1) + assert_equal(counter, 1) + cron.update(1) + assert_equal(counter, 2) + end) + + test( 'Should pass on parameters to the function, if specified', function() + cron.after(1, count, 2) + cron.update(1) + assert_equal(counter, 2) + end) + end) + + + end)