diff --git a/changes.md b/changes.md index f9b455e..50ed08e 100644 --- a/changes.md +++ b/changes.md @@ -4,6 +4,26 @@ Table of contents --- [Update 14.1.0 - A whole new world of possibilities](#update-1410---a-whole-new-world-of-possibilities)
[Update 14.0.0 - Consistency, Additions and Stability](#update-1400-consistency-additions-and-stability)
[Update 13.1.0 - Bug fixes and features added](#update-1310-bug-fixes-and-features-added)
[Update 13.0.0 - Added some documentation, and some new features too check it out!](#update-1300-added-some-documentation-and-some-new-features-too-check-it-out)
[Update 12.2.2 - Time for some more bug fixes!](#update-1222-time-for-some-more-bug-fixes)
[Update 12.2.1 - Time for some bug fixes!](#update-1221-time-for-some-bug-fixes)
[Update 12.2.0 - The chains of binding](#update-1220---the-chains-of-binding)
[Update 12.1.0 - Threads just can't hold on anymore](#update-1210---threads-just-cant-hold-on-anymore)
[Update: 12.0.0 - Big update (Lots of additions some changes)](#update-1200-big-update-lots-of-additions-some-changes)
[Update: 1.11.1 - Small Clarification on Love](#update-1111---small-clarification-on-love)
[Update: 1.11.0](#update-1110)
[Update: 1.10.0](#update-1100)
[Update: 1.9.2](#update-192)
[Update: 1.9.1 - Threads can now argue](#update-191---threads-can-now-argue)
[Update: 1.9.0](#update-190)
[Update: 1.8.7](#update-187)
[Update: 1.8.6](#update-186)
[Update: 1.8.5](#update-185)
[Update: 1.8.4](#update-184)
[Update: 1.8.3 - Mainloop recieves some needed overhauling](#update-183---mainloop-recieves-some-needed-overhauling)
[Update: 1.8.2](#update-182)
[Update: 1.8.1](#update-181)
[Update: 1.7.6](#update-176)
[Update: 1.7.5](#update-175)
[Update: 1.7.4](#update-174)
[Update: 1.7.3](#update-173)
[Update: 1.7.2](#update-172)
[Update: 1.7.1 - Bug Fixes Only](#update-171---bug-fixes-only)
[Update: 1.7.0 - Threading the systems](#update-170---threading-the-systems)
[Update: 1.6.0](#update-160)
[Update: 1.5.0](#update-150)
[Update: 1.4.1 (4/10/2017) - First Public release of the library](#update-141-4102017---first-public-release-of-the-library)
[Update: 1.4.0 (3/20/2017)](#update-140-3202017)
[Update: 1.3.0 (1/29/2017)](#update-130-1292017)
[Update: 1.2.0 (12.31.2016)](#update-120-12312016)
[Update: 1.1.0](#update-110)
[Update: 1.0.0](#update-100)
[Update: 0.6.3](#update-063)
[Update: 0.6.2](#update-062)
[Update: 0.6.1-6](#update-061-6)
[Update: 0.5.1-6](#update-051-6)
[Update: 0.4.1](#update-041)
[Update: 0.3.0 - The update that started it all](#update-030---the-update-that-started-it-all)
[Update: EventManager 2.0.0](#update-eventmanager-200)
[Update: EventManager 1.2.0](#update-eventmanager-120)
[Update: EventManager 1.1.0](#update-eventmanager-110)
[Update: EventManager 1.0.0 - Error checking](#update-eventmanager-100---error-checking)
[Version: EventManager 0.0.1 - In The Beginning things were very different](#version-eventmanager-001---in-the-beginning-things-were-very-different) +# Update 14.2.0 - State Saving reworked! +Full Update Showcase +--- +```lua +package.path="?.lua;?/init.lua;?.lua;"..package.path +local multi, thread = require("multi"):init() +GLOBAL,THREAD = require("multi.integration.lanesManager"):init() +``` +Going Forward: +--- +- +Added: +--- +- +Changed: +--- +- +Removed: +--- +- multi:newTrigger() — Connections do everything this thing could do and more. I plan on removing bloat features from the library anyway # Update 14.1.0 - A whole new world of possibilities Full Update Showcase --- diff --git a/multi/init.lua b/multi/init.lua index fad75b0..34ddcec 100644 --- a/multi/init.lua +++ b/multi/init.lua @@ -21,7 +21,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] -local bin = pcall(require,"bin") local multi = {} local clock = os.clock local thread = {} @@ -42,9 +41,7 @@ multi.fps = 60 multi.Type = "mainprocess" multi.Rest = 0 multi._type = type -multi.Jobs = {} multi.queue = {} -multi.jobUS = 2 multi.clock = os.clock multi.time = os.time multi.LinkedPath = multi @@ -198,6 +195,8 @@ function multi:setPriority(s) elseif type(s)=='string' then if s:lower()=='core' or s:lower()=='c' then self.Priority=self.Priority_Core + elseif s:lower()=="very high" or s:lower()=="vh" then + self.Priority=self.Priority_Very_High elseif s:lower()=='high' or s:lower()=='h' then self.Priority=self.Priority_High elseif s:lower()=='above' or s:lower()=='a' then @@ -208,6 +207,8 @@ function multi:setPriority(s) self.Priority=self.Priority_Below_Normal elseif s:lower()=='low' or s:lower()=='l' then self.Priority=self.Priority_Low + elseif s:lower()=="very low" or s:lower()=="vl" then + self.Priority=self.Priority_Very_Low elseif s:lower()=='idle' or s:lower()=='i' then self.Priority=self.Priority_Idle end @@ -458,26 +459,6 @@ function multi:reallocate(o,n) self.Active=true end multi.Reallocate=multi.Reallocate -function multi:setJobSpeed(n) - self.jobUS=n - return self -end -function multi:hasJobs() - return #self.Jobs>0,#self.Jobs -end -function multi:getJobs() - return #self.Jobs -end -function multi:removeJob(name) - local count = 0 - for i=#self.Jobs,1,-1 do - if self.Jobs[i][2]==name then - table.remove(self.Jobs,i) - count = count + 1 - end - end - return count -end function multi:FreeMainEvent() self.func={} end @@ -656,116 +637,6 @@ function multi:newBase(ins) _tid = _tid + 1 return c end -function multi:newProcessor(file) - if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end - local c = {} - setmetatable(c, self) - c.Parent=self - c.Active=true - c.func={} - c.Type='process' - c.Mainloop={} - c.Garbage={} - c.Children={} - c.Active=false - c.Rest=0 - c.Jobs={} - c.queue={} - c.jobUS=2 - c.l=self:newLoop(function(self,dt) - if self.link.Active then - c:uManager() - end - end) - c.l.link = c - c.l.Type = "processor" - function c:getController() - return c.l - end - function c:Start() - self.Active = true - return self - end - function c:Resume() - self.Active = false - return self - end - function c:setName(name) - c.l.Name = name - return self - end - function c:Pause() - if self.l then - self.l:Pause() - end - return self - end - function c:Remove() - if self.Type == "process" then - self:__Destroy() - self.l:Destroy() - else - self:__Destroy() - end - end - function c:Destroy() - if self == c then - self.l:Destroy() - else - for i = #c.Mainloop,1,-1 do - if c.Mainloop[i] == self then - table.remove(c.Mainloop,i) - break - end - end - end - end - if file then - self.Cself=c - loadstring('local process=multi.Cself '..io.open(file,'rb'):read('*all'))() - end - self:create(c) - return c -end -function multi:newTimer() - local c={} - c.Type='timer' - local time=0 - local count=0 - local paused=false - function c:Start() - time=os.clock() - return self - end - function c:Get() - if self:isPaused() then return time end - return (clock()-time)+count - end - function c:isPaused() - return paused - end - c.Reset=c.Start - function c:Pause() - time=self:Get() - paused=true - return self - end - function c:Resume() - paused=false - time=os.clock()-time - return self - end - function c:tofile(path) - local m=bin.new() - count=count+self:Get() - m:addBlock(self.Type) - m:addBlock(count) - m:tofile(path) - return self - end - self:create(c) - return c -end function multi:newConnector() local c = {Type = "connector"} return c @@ -773,6 +644,7 @@ end local CRef = { Fire = function() end } +local ignoreconn = true function multi:newConnection(protect,func,kill) local c={} c.callback = func @@ -939,39 +811,121 @@ function multi:newConnection(protect,func,kill) m:tofile(path) return self end + if not(ignoreconn) then + multi:create(c) + end return c end multi.OnObjectCreated=multi:newConnection() multi.OnObjectDestroyed=multi:newConnection() -function multi:newJob(func,name) - if not(self.Type=='mainprocess' or self.Type=='process') then error('Can only create an object on multi or an interface obj') return false end +ignoreconn = false +function multi:newProcessor(file) + if not(self.Type=='mainprocess') then error('Can only create an interface on the multi obj') return false end local c = {} - if self.Type=='process' then - setmetatable(c, self.Parent) - else - setmetatable(c, self) - end + setmetatable(c, self) + c.Parent=self c.Active=true c.func={} - c.Parent=self - c.Type='job' - c.trigfunc=func or function() end - function c:Act() - self:trigfunc(self) + c.Type='process' + c.Mainloop={} + c.Garbage={} + c.Children={} + c.Active=false + c.Rest=0 + c.queue={} + c.l=self:newLoop(function(self,dt) + if self.link.Active then + c:uManager() + end + end) + c.l.link = c + c.l.Type = "processor" + function c:getController() + return c.l end - table.insert(self.Jobs,{c,name}) - if self.JobRunner==nil then - self.JobRunner=self:newAlarm(self.jobUS):setName("multi.jobHandler") - self.JobRunner:OnRing(function(self) - if #self.Parent.Jobs>0 then - if self.Parent.Jobs[1] then - self.Parent.Jobs[1][1]:Act() - table.remove(self.Parent.Jobs,1) + function c:Start() + self.Active = true + return self + end + function c:Resume() + self.Active = false + return self + end + function c:setName(name) + c.l.Name = name + return self + end + function c:Pause() + if self.l then + self.l:Pause() + end + return self + end + function c:Remove() + if self.Type == "process" then + self:__Destroy() + self.l:Destroy() + else + self:__Destroy() + end + end + function c:Destroy() + if self == c then + self.l:Destroy() + else + for i = #c.Mainloop,1,-1 do + if c.Mainloop[i] == self then + table.remove(c.Mainloop,i) + break end end - self:Reset(self.Parent.jobUS) - end) + end end + if file then + self.Cself=c + loadstring('local process=multi.Cself '..io.open(file,'rb'):read('*all'))() + end + self:create(c) + return c +end +function multi:newTimer() + local c={} + c.Type='timer' + local time=0 + local count=0 + local paused=false + function c:Start() + time=os.clock() + return self + end + function c:Get() + if self:isPaused() then return time end + return (clock()-time)+count + end + function c:isPaused() + return paused + end + c.Reset=c.Start + function c:Pause() + time=self:Get() + paused=true + return self + end + function c:Resume() + paused=false + time=os.clock()-time + return self + end + function c:tofile(path) + local m=bin.new() + count=count+self:Get() + m:addBlock(self.Type) + m:addBlock(count) + m:tofile(path) + return self + end + self:create(c) + return c end function multi.nextStep(func) ncount = ncount+1 @@ -1093,6 +1047,7 @@ end function multi:newFunction(func) local c={} c.func=func + c.Type = "mfunc" mt={ __index=multi, __call=function(self,...) @@ -1225,17 +1180,6 @@ end function multi:setTimeout(func,t) multi:newThread(function() thread.sleep(t) func() end) end -function multi:newTrigger(func) - local c={} - c.Type='trigger' - c.trigfunc=func or function() end - function c:Fire(...) - self:trigfunc(...) - return self - end - self:create(c) - return c -end function multi:newTStep(start,reset,count,set) local c=self:newBase() think=1 @@ -1643,6 +1587,7 @@ function multi:newThread(name,func,...) end c.creationTime = os.clock() threadid = threadid + 1 + multi.create(multi,c) return c end function multi.initThreads(justThreads) @@ -1819,7 +1764,7 @@ function multi:newService(func) -- Priority managed threads local time local p = multi.Priority_Normal local ap - local task + local task = thread.sleep local scheme = 1 function c.Start() time = multi:newTimer() @@ -1878,11 +1823,48 @@ function multi:newService(func) -- Priority managed threads end function c:SetPriority(pri) if type(self)=="number" then pri = self end - p = pri - c.SetScheme(scheme) - end + p = pri + c.SetScheme(scheme) + end + multi.create(multi,c) return c end +multi.Jobs = multi:newService(function(self,jobs) + local job = table.remove(jobs,1) + if job and job.removed==nil then + job.func() + end +end) +multi.Jobs.OnStarted(function(self,jobs) + function self:newJob(func,name) + table.insert(jobs,{ + func = func, + name = name, + removeJob = function(self) self.removed = true end + }) + end + function self:getJobs(name) + local tab = {} + if not name then return jobs end + for i=1,#jobs do + if name == jobs[i].name then + table.insert(tab,jobs[i]) + end + end + return tab + end + function self:removeJobs(name) + for i=1,#jobs do + if name ~= nil and name == jobs[i].name then + jobs[i]:removeJob() + elseif name == nil then + jobs[i]:removeJob() + end + end + end +end) +multi.Jobs.SetPriority(multi.Priority_Normal) +multi.Jobs.Start() function multi:newThreadedProcess(name) local c = {} local holding = false @@ -1911,9 +1893,7 @@ function multi:newThreadedProcess(name) c.Rest=0 c.updaterate=.01 c.restRate=.1 - c.Jobs={} c.queue={} - c.jobUS=2 c.rest=false function c:getController() return nil @@ -1962,6 +1942,7 @@ function multi:newThreadedProcess(name) c:uManager() end end) + multi:create(c) return c end function multi:newHyperThreadedProcess(name) @@ -2008,9 +1989,7 @@ function multi:newHyperThreadedProcess(name) c.Rest=0 c.updaterate=.01 c.restRate=.1 - c.Jobs={} c.queue={} - c.jobUS=2 c.rest=false function c:getController() return nil @@ -2052,6 +2031,7 @@ function multi:newHyperThreadedProcess(name) return self end c.Hold=c.Sleep + multi:create(c) return c end -- Multi runners diff --git a/multi/integration/sterilization.lua b/multi/integration/sterilization.lua index 901c359..e308273 100644 --- a/multi/integration/sterilization.lua +++ b/multi/integration/sterilization.lua @@ -21,37 +21,45 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] +local bin = pcall(require,"bin") +if not bin then return error("The bin library is required to use sterilization!") end local multi,thread = require("multi"):init() +local sterilizer = {} --------------------- -- State Saving Stuff --------------------- -function multi:IngoreObject() +multi.OnObjectCreated(function(obj) + print(obj.Type) +end) + +function IngoreObject() -- Tells system to not sterilize this object end -function multi:ToString() +function sterilizer:ToString() -- Turns the object into a string end -function multi:newFromString(str) +function sterilizer:newFromString(str) -- Creates an object from string end -function multi:ToFile(path) +function sterilizer:ToFile(path) -- Turns multi object into a string end -function multi:fromFile(path) +function sterilizer:fromFile(path) -- Loads multi object form file end -function multi:SetStateFlag(opt) +function sterilizer:SetStateFlag(opt) -- manage the states end -function multi:quickStateSave(b) +function sterilizer:quickStateSave(b) -- enables quick state saving end -function multi:saveState(path,opt) +function sterilizer:saveState(path,opt) -- Saves entire state to file end -function multi:loadState(path) +function sterilizer:loadState(path) -- Loads entire state from file end -function multi:setDefualtStateFlag(opt) +function sterilizer:setDefualtStateFlag(opt) -- sets the default flags for managing states -end \ No newline at end of file +end +return sterilizer \ No newline at end of file diff --git a/test.lua b/test.lua index 71d3ea1..473f63e 100644 --- a/test.lua +++ b/test.lua @@ -1,100 +1,15 @@ -package.path="?.lua;?/init.lua;?.lua;"..package.path -local multi, thread = require("multi"):init() -GLOBAL,THREAD = require("multi.integration.lanesManager"):init() -serv = multi:newService(function(self,data) - print("Service Uptime: ",self:GetUpTime(),data.test) +package.path="?.lua;?/init.lua;?.lua;?/?/init.lua;"..package.path +local multi,thread = require("multi"):init() +--local sterilizer = require("multi.integration.sterilization") +multi.Jobs:newJob(function() + print("job called") end) -serv.OnError(function(...) - print(...) +multi.Jobs:newJob(function() + print("job called2") end) -serv.OnStarted(function(self,data) - print("Started!",self.Type,data) - data.test = "Testing..." - -- self as reference to the object and data is a reference to the datatable that the service has access to +multi.Jobs:newJob(function() + print("job called3") end) -serv:Start() -serv:SetPriority(multi.Priority_Idle) -t = THREAD:newFunction(function(...) - print("This is a system threaded function!",...) - THREAD.sleep(1) -- This is handled within a system thread! Note: this creates a system thread that runs then ends. - return "We done!" -end) -print(t("hehe",1,2,3,true).connect(function(...) - print("connected:",...) -end)) -- The same features that work with thread:newFunction() are here as well -multi.OnLoad(function() - print("Code Loaded!") -- Connect to the load event -end) -t = os.clock() -co = 0 -multi.OnExit(function(n) - print("Code Exited: ".. os.clock()-t .." Count: ".. co) -- Lets print when things have ended -end) -test = thread:newFunction(function() - thread.sleep(1) -- Internally this throws a yield call which sends to the scheduler to sleep 1 second for this thread! - return 1,math.random(2,100) -end) -multi:newThread(function() - while true do - thread.skip() -- Even though we have a few metamethods "yielding" I used this as an example of things still happening and counting. It connects to the Code Exited event later on. - co = co + 1 - end -end) --- We can get around the yielding across metamethods by using a threadedFunction --- For Example -example = {} -setmetatable(example,{ - __newindex = function(t,k,v) -- Using a threaded function inside of a normal function - print("Inside metamethod",t,k,v) - local a,b = test().wait() -- This function holds the code and "yields" see comment inside the test function! - -- we should see a 1 seconde delay since the function sleeps for a second than returns - print("We did it!",a,b) - rawset(t,k,v) - -- This means by using a threaded function we can get around the yielding across metamethods. - -- This is useful if you aren't using luajit, or if you using lua in an enviroment that is on version 5.1 - -- There is a gotcha however, if using code that was meant to work with another coroutine based scheduler this may not work - end, - __index = thread:newFunction(function(t,k,v) -- Using a threaded function as the metamethod - -- This works by returning a table with a __call metamethod. Will this work? Will lua detect this as a function or a table? - thread.sleep(1) - return "You got a string" - end,true) -- Tell the code to force a wait. We need to do this for metamethods -}) -example["test"] = "We set a variable!" -print(example["test"]) -print(example.hi) --- When not in a threaded enviroment at root level we need to tell the code that we are waiting! Alternitavely after the function argument we can pass true to force a wait -c,d = test().wait() -print(c,d) -a,b = 6,7 -multi:newThread(function() - a,b = test().wait() -- Will modify Global - print("Waited:",a,b) - - --This returns instantly even though the function isn't done! - print("called") - test().connect(function(a,b) - print("Connected:",a,b) - os.exit() - end) - print("Returned") - -- This waits for the returns since we are demanding them -end) -local test = multi:newSystemThreadedJobQueue(4) -- Load up a queue that has 4 running threads -func = test:newFunction("test",function(a) -- register a function on the queue that has an async function feature - test2() -- Call the other registered function on the queue - return a..a -end,true) -func2 = test:newFunction("test2",function(a) - print("ooo") - console = THREAD:getConsole() - console.print("Hello!",true) -end,true) -- When called internally on the job queue the function is a normal sync function and not an async function. -multi:scheduleJob({min = 15, hour = 14},function() - -- This function will be called once everyday at 2:15 - -- Using a combination of the values above you are able to schedule a time -end) -print(func("1")) -print(func("Hello")) -print(func("sigh")) +multi.Jobs.SetScheme(1) +multi.Jobs.SetPriority(multi.Priority_Core) multi:lightloop() \ No newline at end of file diff --git a/time/init.lua b/time/init.lua deleted file mode 100644 index a2b35cf..0000000 --- a/time/init.lua +++ /dev/null @@ -1,570 +0,0 @@ --------------------------------------------------------------------------------- --- A library for the manipulation of dates and periods according to the --- Gregorian calendar. --- --- Credit: the Gregorian calendar routines contained in this library are --- ported from Claus Tøndering calendar algorithms: --- http://www.tondering.dk/main/index.php/calendar-information . --- --- Copyright (C) 2011-2016 Stefano Peluchetti. All rights reserved. --------------------------------------------------------------------------------- - -local ffi = require "ffi" - -local C = ffi.C -local format = string.format -local floor, min = math.floor, math.min -local type, new, istype, tonumber = type, ffi.new, ffi.istype, tonumber - -local int64_ct = ffi.typeof("int64_t") - -local function T_int(x) - if not (type(x) == "number" and x == floor(x)) then - error("integer number expected") - end -end - -local function T_same(x, y) - if not istype(x, y) then - error("same type expected") - end -end - --- Period ---------------------------------------------------------------------- --- Return string representation for a positive period. -local function posptostr(h, m, s, ms) - return format("%02i:%02i:%02i.%06i", h, m, s, ms) -end - -local p_ct - -local function T_period(x) - if not istype(p_ct, x) then - error("period expected") - end -end - -local function p_64(ticks) - return new(p_ct, ticks) -end - -local function pfirst(x, y) - if istype(p_ct, y) then - return y, x - else - return x, y - end -end - -local p_mt = { - __new = function(ct, h, m, s, ms) - h = h or 0; m = m or 0; s = s or 0; ms = ms or 0; - T_int(h); T_int(m); T_int(s); T_int(ms); - return new(ct, h*(1e6*60*60LL)+m*(1e6*60LL)+s*(1e6*1LL)+ms) - end, - copy = function(self) - return new(p_ct, self) - end, - __eq = function(self, rhs) T_same(self, rhs) - return self._ticks == rhs._ticks - end, - __lt = function(self, rhs) T_same(self, rhs) - return self._ticks < rhs._ticks - end, - __le = function(self, rhs) T_same(self, rhs) - return self._ticks <= rhs._ticks - end, - __add = function(self, rhs) T_same(self, rhs) -- Commutative. - return p_64(self._ticks + rhs._ticks) - end, - __sub = function(self, rhs) T_same(self, rhs) - return p_64(self._ticks - rhs._ticks) - end, - __unm = function(self) - return p_64(-self._ticks) - end, - __mul = function(self, rhs) -- Commutative. - local p, n = pfirst(self, rhs) - T_int(n) - return p_64(p._ticks*n) - end, - -- Approximate ratio, non-reversible in both cases. - __div = function(self, rhs) - T_period(self) -- Disallow (not-a-period)/period. - if type(rhs) == "number" then - return p_64(self._ticks/rhs) - elseif istype(p_ct, rhs) then - return tonumber(self._ticks)/tonumber(rhs._ticks) - else - error("unexpected type") - end - end, - __tostring = function(self) - local h, m, s, ms = self:parts() - if self._ticks >= 0 then - return posptostr(h, m, s, ms) - else - return "-"..posptostr(-h, -m, -s, -ms) - end - end, - ticks = function(self) -- Expose int64_t. - return self._ticks - end, - microseconds = function(self) - return tonumber(self._ticks%1e6) - end, - seconds = function(self) - return tonumber((self._ticks/1e6)%60) - end, - minutes = function(self) - return tonumber((self._ticks/(1e6*60))%60) - end, - hours = function(self) - return tonumber(self._ticks/(1e6*60*60)) - end, - parts = function(self) - return self:hours(), self:minutes(), self:seconds(), self:microseconds() - end, - tomicroseconds = function(self) - return tonumber(self._ticks) - end, - tomilliseconds = function(self) - return tonumber(self._ticks)/1e3 - end, - toseconds = function(self) - return tonumber(self._ticks)/1e6 - end, - tominutes = function(self) - return tonumber(self._ticks)/(1e6*60) - end, - tohours = function(self) - return tonumber(self._ticks)/(1e6*60*60) - end, -} -p_mt.__index = p_mt - -p_ct = ffi.metatype("struct { int64_t _ticks; }", p_mt) - -local function weeks(x) T_int(x) return p_64(x*(1e6*60*60*24*7LL)) end -local function days(x) T_int(x) return p_64(x*(1e6*60*60*24LL)) end -local function hours(x) T_int(x) return p_64(x*(1e6*60*60LL)) end -local function minutes(x) T_int(x) return p_64(x*(1e6*60LL)) end -local function seconds(x) T_int(x) return p_64(x*(1e6*1LL)) end -local function milliseconds(x) T_int(x) return p_64(x*(1e3*1LL)) end -local function microseconds(x) T_int(x) return p_64(x*1LL) end - -local function toperiod(x) - if type(x) == "string" then - local f1, l1, h, m, s, ms = x:find("(%d+):(%d+):(%d+).(%d+)") - if h == nil or ms == nil or l1 ~= #x then - error("'"..x.."' is not a string representation of a period") - end - local ton = tonumber - return p_ct(ton(h), ton(m), ton(s), ton(ms)) - elseif istype(int64_ct, x) then - return p_64(x) - else - error("unexpected type") - end -end - --- Months ---------------------------------------------------------------------- -local months_mt = { - __new = function(ct, x) T_int(x) - return new(ct, x) - end, -} - -local months_ct = ffi.metatype("struct { int32_t _m; }", months_mt) - --- Years ----------------------------------------------------------------------- -local years_mt = { - __new = function(ct, x) T_int(x) - return new(ct, x) - end, -} - -local years_ct = ffi.metatype("struct { int32_t _y; }", years_mt) - --- Date ------------------------------------------------------------------------ --- It's date(1582, 1, 1): -local d_ticks_min = 198622713600000000LL --- It's date(9999,12,31) + period(23, 59, 59, 999999): -local d_ticks_max = 464269103999999999LL - -local d_ct - -local function T_date(x) - if not istype(d_ct, x) then - error("date expected") - end -end - -local function T_date_range(ticks) - if not (d_ticks_min <= ticks and ticks <= d_ticks_max) then - error("resulting date is outside the allowed range") - end -end - -local function d_64(ticks) T_date_range(ticks) - return new(d_ct, ticks) -end - -local function dfirst(x, y) - if istype(d_ct, y) then - return y, x - else - return x, y - end -end - --- 1582 adoption, 9999 to keep 4 chars for years part: -local function T_year(year) T_int(year) - if not (1582 <= year and year <= 9999) then - error("year "..year.." outside the allowed range [1582, 9999]") - end -end - -local function T_month(month) T_int(month) - if not (1 <= month and month <= 12) then - error("month "..month.." outside the allowed range [1, 12]") - end -end - -local function isleapyear(year) T_year(year) - return year%4 == 0 and (year%100 ~= 0 or year%400 == 0) -end - -local eom = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } - -local function endofmonth(year, month) T_year(year) T_month(month) - return (month == 2 and isleapyear(year)) and 29 or eom[month] -end - -local function T_day(year, month, day) - if not (1 <= day and day <= endofmonth(year, month)) then - error(year.."-"..month.."-"..day.." is not a valid date") - end -end - -local function weekday(year, month, day) T_year(year) T_month(month) - T_day(year, month, day) - local a = floor((14 - month)/12) - local y = year - a - local m = month + 12*a - 2 - local d = (day + y + floor(y/4) - floor(y/100) + floor(y/400) + - floor((31*m)/12)) % 7 - return d == 0 and 7 or d -- Days of week from 1 = Monday to 7 = Sunday. -end - -local function shift_months(y, m, deltam) - local newm = (m - 1 + deltam) % 12 + 1 - local newy = y + floor((m - 1 + deltam)/12) - T_year(newy) - T_month(newm) - return newy, newm -end - -local function ymd_to_julian(year, month, day) - -- Range of numbers suffices for this: - local a = floor((14 - month)/12) - local y = year + 4800 - a - local m = month + 12*a - 3 - return day + floor((153*m + 2)/5) + 365*y + floor(y/4) - floor(y/100) + - floor(y/400) - 32045 -end - -local function julian_to_ymd(julian) - -- Range of numbers suffices for this: - local a = julian + 32044 - local b = floor((4*a + 3)/146097) - local c = a - floor((146097*b)/4) - local d = floor((4*c + 3)/1461) - local e = c - floor((1461*d)/4) - local m = floor((5*e + 2)/153) - local day = e - floor((153*m + 2)/5) + 1 - local month = m + 3 - 12*floor(m/10) - local year = 100*b + d - 4800 + floor(m/10) - return year, month, day -end - --- Assumes only violation of valid date may be in day outside of end of month --- due to months and years shifts. Cap the day and returns a valid date. -local function valid_date_cap_day(year, month, day) - day = min(day, endofmonth(year, month)) - return d_ct(year, month, day) -end - -local d_mt = { - __new = function(ct, year, month, day) T_year(year) T_month(month) - T_day(year, month, day) - return new(ct, ymd_to_julian(year, month, day)*(86400LL*1e6)) - end, - copy = function(self) - return new(d_ct, self) - end, - __eq = p_mt.__eq, - __lt = p_mt.__lt, - __le = p_mt.__le, - __add = function(self, rhs) -- Commutative. - local d, s = dfirst(self, rhs) - if istype(p_ct, s) then - return d_64(d._ticks + s._ticks) - elseif istype(months_ct, s) then - local year, month, day = d:ymd() - year, month = shift_months(year, month, s._m) - return valid_date_cap_day(year, month, day) + d:period() - elseif istype(years_ct, s) then - local year, month, day = d:ymd() - year = year + s._y - return valid_date_cap_day(year, month, day) + d:period() - else - error("unexpected type") - end - end, - __sub = function(self, rhs) - T_date(self) -- Disallow (not-a-date)-date. - if istype(p_ct, rhs) then - return d_64(self._ticks - rhs._ticks) - elseif istype(months_ct, rhs) then - local year, month, day = self:ymd() - year, month = shift_months(year, month, -rhs._m) - return valid_date_cap_day(year, month, day) + self:period() - elseif istype(years_ct, rhs) then - local year, month, day = self:ymd() - year = year - rhs._y - return valid_date_cap_day(year, month, day) + self:period() - elseif istype(d_ct, rhs) then - return p_64(self._ticks - rhs._ticks) - else - error("unexpected type") - end - end, - __tostring = function(self) - local year, month, day = self:ymd() - local h, m, s, ms = self:period():parts() - return format("%i-%02i-%02iT", year, month, day)..posptostr(h, m, s, ms) - end, - ticks = p_mt.ticks, - ymd = function(self) - local julian = tonumber(self._ticks/(86400LL*1e6)) - return julian_to_ymd(julian) - end, - year = function(self) local y, m, d = self:ymd() return y end, - month = function(self) local y, m, d = self:ymd() return m end, - day = function(self) local y, m, d = self:ymd() return d end, - period = function(self) - return p_64(self._ticks%(86400LL*1e6)) - end, - isleapyear = function(self) - local y = self:ymd() - return isleapyear(y) - end, - endofmonth = function(self) - local y, m = self:ymd() - return endofmonth(y, m) - end, - weekday = function(self) - local y, m, d = self:ymd() - return weekday(y, m, d) - end, -} -d_mt.__index = d_mt - -d_ct = ffi.metatype("struct { int64_t _ticks; }", d_mt) - -local function todate(x) - if type(x) == "string" then - local f1, l1, year, month, day, h, m, s, ms = - x:find("(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+).(%d+)") - if year == nil or ms == nil or l1 ~= #x then - error("'"..x.."' is not a string representation of a date") - end - local ton = tonumber - return d_ct(ton(year), ton(month), ton(day)) + - p_ct(ton(h), ton(m), ton(s), ton(ms)) - elseif istype(int64_ct, x) then - return d_64(x) - else - error("unexpected type") - end -end - --- System-dependent functions -------------------------------------------------- -local nowlocal, nowutc, sleep - -if jit.os == "Windows" then -- On Windows sizeof(long) == 4 on both x86 and x64. - ffi.cdef[[ - typedef unsigned long DWORD; - typedef unsigned short WORD; - typedef unsigned __int64 ULONGLONG; - - typedef struct _SYSTEMTIME { - WORD wYear; - WORD wMonth; - WORD wDayOfWeek; - WORD wDay; - WORD wHour; - WORD wMinute; - WORD wSecond; - WORD wMilliseconds; - } SYSTEMTIME, *PSYSTEMTIME; - - typedef union _ULARGE_INTEGER { - struct { - DWORD LowPart; - DWORD HighPart; - }; - struct { - DWORD LowPart; - DWORD HighPart; - } u; - ULONGLONG QuadPart; - } ULARGE_INTEGER, *PULARGE_INTEGER; - - typedef struct _FILETIME { - DWORD dwLowDateTime; - DWORD dwHighDateTime; - } FILETIME, *PFILETIME; - - void GetLocalTime( - PSYSTEMTIME lpSystemTime - ); - //void GetSystemTime( - // PSYSTEMTIME lpSystemTime - //); - void GetSystemTimeAsFileTime( - PFILETIME lpSystemTimeAsFileTime - ); - void GetSystemTimeAsPreciseFileTime( - PFILETIME lpSystemTimeAsFileTime - ); - void Sleep( - DWORD dwMilliseconds - ); - ]] - - local st = ffi.new("SYSTEMTIME") - local ft = ffi.new("FILETIME") - local ul = ffi.new("ULARGE_INTEGER") - - nowlocal = function() - C.GetLocalTime(st) - return d_ct(st.wYear, st.wMonth, st.wDay) + p_ct(st.wHour, st.wMinute, - st.wSecond, st.wMilliseconds*1000) - end - - local epoch_offset = d_ct(1601, 1, 1):ticks() - local function if_available(lib, fname) - local ok, f = pcall(function() return lib[fname] end) - return ok and f - end - local get_system_time = if_available(C, 'GetSystemTimeAsPreciseFileTime') - or C.GetSystemTimeAsFileTime - - nowutc = function() - -- Resolution: 1 microsecond. - -- Accuracy : 1 millisecond up to Windows 7, higher otherwise. - get_system_time(ft) - ul.LowPart = ft.dwLowDateTime - ul.HighPart = ft.dwHighDateTime - return new(d_ct, epoch_offset + ul.QuadPart/10) - end - - sleep = function(p) - if p < p_ct() then - error("cannot sleep a negative amount of time") - end - C.Sleep(p:ticks()/1000) - end - -else -- Linux and OSX. - ffi.cdef[[ - typedef long time_t; - typedef int useconds_t; - - typedef struct timeval { - long tv_sec; - int tv_usec; - } timeval; - typedef struct tm { - int tm_sec; - int tm_min; - int tm_hour; - int tm_mday; - int tm_mon; - int tm_year; - int tm_wday; - int tm_yday; - int tm_isdst; - long tm_gmtoff; - char *tm_zone; - } tm; - - int gettimeofday( - struct timeval * restrict, - void * restrict - ); - struct tm *localtime( - const time_t * - ); - int usleep( - useconds_t useconds - ); - ]] - - -- C.host_get_clock_service it's slower and we don't need higher resolution. - -- C.mach_absolute_time does not report real clock. - local epoch_offset = d_ct(1970, 1, 1):ticks() - local tv = ffi.new("timeval[1]") - local tt = ffi.new("time_t[1]") - - nowlocal = function() - C.gettimeofday(tv, nil) - tt[0] = tv[0].tv_sec - local tm = C.localtime(tt) - return d_ct(1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday) + - p_ct(tm.tm_hour, tm.tm_min, tm.tm_sec, tv[0].tv_usec) - end - - nowutc = function() - -- Resolution: 1 microsecond. - -- Accuracy : 1 microsecond. - C.gettimeofday(tv, nil) - return new(d_ct, epoch_offset + tv[0].tv_sec*1000000LL + tv[0].tv_usec) - end - - sleep = function(p) - if p < p_ct() then - error("cannot sleep a negative amount of time") - end - C.usleep(p:ticks()) - end -end - -return { - period = p_ct, - toperiod = toperiod, - - weeks = weeks, - days = days, - hours = hours, - minutes = minutes, - seconds = seconds, - milliseconds = milliseconds, - microseconds = microseconds, - - date = d_ct, - todate = todate, - - isleapyear = isleapyear, - endofmonth = endofmonth, - weekday = weekday, - - months = months_ct, - years = years_ct, - - sleep = sleep, - nowlocal = nowlocal, - nowutc = nowutc, -}