From ce9a30eac9cfbe18700b4745879d6574e5b6046a Mon Sep 17 00:00:00 2001 From: "Johnny C. Lam" Date: Thu, 31 Oct 2013 19:06:14 +0000 Subject: [PATCH] True time-span implementation. A time-span is a union of continuous interval subsets of the real number line (0, infinity). Internally represented by an array of numbers representing alternately the left and right endpoints of an interval. Point sets and empty sets are both considered empty. Pre-allocate tables when creating parse tree nodes in OvaleCompile to be reused for time-span evaluation within OvaleBestAction. Convert OvaleBestAction to use time-spans for correctness when dealing with arbitrary unions and intersections of time intervals when evaluating nodes. This fixes ticket 306 - "and" "or" bugged by @Wiljo. git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@1117 d5049fe3-3747-40f7-a4b5-f36d6801af5f --- OvaleBestAction.lua | 393 ++++++++++++++++------------ OvaleCompile.lua | 55 ++-- OvaleCondition.lua | 13 +- OvaleFrame.lua | 7 +- OvaleTimeSpan.lua | 726 ++++++++++++++++++++++++++++++++++++++++++++++----- compiler.pl | 2 + 6 files changed, 946 insertions(+), 250 deletions(-) diff --git a/OvaleBestAction.lua b/OvaleBestAction.lua index 75c75a7..60bf989 100644 --- a/OvaleBestAction.lua +++ b/OvaleBestAction.lua @@ -18,6 +18,7 @@ local OvaleCondition = Ovale.OvaleCondition local OvaleData = Ovale.OvaleData local OvaleEquipement = Ovale.OvaleEquipement local OvalePaperDoll = Ovale.OvalePaperDoll +local OvalePool = Ovale.OvalePool local OvalePower = Ovale.OvalePower local OvaleSpellBook = Ovale.OvaleSpellBook local OvaleStance = Ovale.OvaleStance @@ -33,9 +34,12 @@ local select = select local strfind = string.find local tonumber = tonumber local tostring = tostring +local wipe = table.wipe local Complement = OvaleTimeSpan.Complement +local CopyTimeSpan = OvaleTimeSpan.CopyTo local HasTime = OvaleTimeSpan.HasTime local Intersect = OvaleTimeSpan.Intersect +local IntersectInterval = OvaleTimeSpan.IntersectInterval local Measure = OvaleTimeSpan.Measure local Union = OvaleTimeSpan.Union local API_GetActionCooldown = GetActionCooldown @@ -56,6 +60,8 @@ local OVALE_DEFAULT_PRIORITY = 3 -- Age of the current computation. local self_serial = 0 +-- Pool of time-span tables. +local self_pool = OvalePool("OvaleBestAction_pool") -- -- @@ -72,22 +78,24 @@ end local function ComputeAction(element) local self = OvaleBestAction + local action = element.params[1] local actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration, actionUsable, actionShortcut, actionIsCurrent, actionEnable, spellId = self:GetActionInfo(element) + local timeSpan = element.timeSpan + timeSpan:Reset() if not actionTexture then - Ovale:Logf("Action %s not found", element.params[1]) - return nil - end - if not (actionEnable and actionEnable > 0) then - Ovale:Logf("Action %s not enabled", element.params[1]) - return nil - end - if element.params.usable == 1 and not actionUsable then - Ovale:Logf("Action %s not usable", element.params[1]) - return nil + Ovale:Logf("Action %s not found", action) + return timeSpan + elseif not (actionEnable and actionEnable > 0) then + Ovale:Logf("Action %s not enabled", action) + return timeSpan + elseif element.params.usable == 1 and not actionUsable then + Ovale:Logf("Action %s not usable", action) + return timeSpan end + -- Set the cast time of the action. if spellId then local si = spellId and OvaleData.spellInfo[spellId] if si and si.casttime then @@ -101,8 +109,8 @@ local function ComputeAction(element) end end if si and si.toggle and actionIsCurrent then - Ovale:Logf("Action %s (toggle) is the current action", element.params[1]) - return nil + Ovale:Logf("Action %s (toggle) is the current action", action) + return timeSpan end else element.castTime = 0 @@ -118,6 +126,7 @@ local function ComputeAction(element) Ovale:Logf("start=%f attenteFinCast=%s [%d]", start, OvaleState.attenteFinCast, element.nodeId) + -- If the action is available before the end of the current spellcast, then wait until we can first cast the action. if start < OvaleState.attenteFinCast then local si = OvaleState.currentSpellId and OvaleData.spellInfo[OvaleState.currentSpellId] if not (si and si.canStopChannelling) then @@ -148,38 +157,44 @@ local function ComputeAction(element) Ovale:Logf("%s start=%f, numTicks=%d, tick=%f, tickTime=%f", spellId, start, numTicks, tick, tickTime) end end - Ovale:Logf("Action %s can start at %f", element.params[1], start) + Ovale:Logf("Action %s can start at %f", action, start) local priority = element.params.priority or OVALE_DEFAULT_PRIORITY - return start, math.huge, priority, element + timeSpan[1], timeSpan[2] = start, math.huge + return timeSpan, priority, element end local function ComputeAnd(element) Ovale:Logf("%s [%d]", element.type, element.nodeId) local self = OvaleBestAction - local startA, endA = self:ComputeBool(element.a) + local timeSpanA = self:ComputeBool(element.a) + local timeSpan = element.timeSpan + -- Short-circuit evaluation of left argument to AND. - if Measure(startA, endA) == 0 then - Ovale:Logf("%s return timespan with measure 0 [%d]", element.type, element.nodeId) - return nil + if Measure(timeSpanA) == 0 then + timeSpan:Reset(timeSpanA) + else + local timeSpanB = self:ComputeBool(element.b) + -- Take intersection of A and B. + timeSpan:Reset() + Intersect(timeSpanA, timeSpanB, timeSpan) end - local startB, endB = self:ComputeBool(element.b) - -- Take intersection of (startA, endA) and (startB, endB). - startA, endA = Intersect(startA, endA, startB, endB) - Ovale:Logf("%s returns %s, %s [%d]", element.type, startA, endA, element.nodeId) - return startA, endA + Ovale:Logf("%s returns %s [%d]", element.type, tostring(timeSpan), element.nodeId) + return timeSpan end local function ComputeArithmetic(element) local self = OvaleBestAction - local startA, endA, _, elementA = self:Compute(element.a) - local startB, endB, _, elementB = self:Compute(element.b) + local timeSpanA, _, elementA = self:Compute(element.a) + local timeSpanB, _, elementB = self:Compute(element.b) + local timeSpan = element.timeSpan + timeSpan:Reset() - -- Take intersection of (startA, endA) and (startB, endB) - startA, endA = Intersect(startA, endA, startB, endB) - if Measure(startA, endA) == 0 then - Ovale:Logf("%s return timespan with measure 0 [%d]", element.type, element.nodeId) - return nil + -- Take intersection of A and B. + Intersect(timeSpanA, timeSpanB, timeSpan) + if Measure(timeSpan) == 0 then + Ovale:Logf("%s return %s [%d]", element.type, tostring(timeSpan), element.nodeId) + return timeSpan, OVALE_DEFAULT_PRIORITY, PutValue(element, 0, 0, 0) end --[[ @@ -230,17 +245,17 @@ local function ComputeArithmetic(element) n = c - z elseif element.operator == "*" then --[[ - A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c - B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z + A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c + B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z A(t)*B(t) = A*B + (t - t0)*[A*z + B*c] + [(t - t0)^2]*(c*z) - = A*B + (t - t0)*[A*z + B*c] + O(t^2) converges everywhere. + = A*B + (t - t0)*[A*z + B*c] + O(t^2) converges everywhere. --]] l = A*B m = atTime n = A*z + B*c elseif element.operator == "/" then --[[ - A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c + A(t) = A(t0) + (t - t0)*c = A + (t - t0)*c B(t) = B(t0) + (t - t0)*z = B + (t - t0)*z A(t)/B(t) = A/B + (t - t0)*[(B*c - A*z)/B^2] + O(t^2) converges when |t - t0| < |B/z|. --]] @@ -253,7 +268,10 @@ local function ComputeArithmetic(element) else bound = abs(B/z) end - startA, endA = Intersect(startA, endA, atTime - bound, atTime + bound) + local scratch = OvaleTimeSpan(self_pool:Get()) + scratch:Reset(timeSpan) + IntersectInterval(scratch, atTime - bound, atTime + bound, timeSpan) + self_pool:Release(scratch) elseif element.operator == "%" then if c == 0 and z == 0 then l = A % B @@ -261,22 +279,27 @@ local function ComputeArithmetic(element) n = 0 else Ovale:Error("Parameters of % must be constants") - return nil + l = 0 + m = 0 + n = 0 + timeSpan:Reset() end end Ovale:Logf("result = %f+(t-%f)*%f [%d]", l, m, n, element.nodeId) - return startA, endA, OVALE_DEFAULT_PRIORITY, PutValue(element, l, m, n) + return timeSpan, OVALE_DEFAULT_PRIORITY, PutValue(element, l, m, n) end local function ComputeCompare(element) local self = OvaleBestAction - local startA, endA, _, elementA = self:Compute(element.a) - local startB, endB, _, elementB = self:Compute(element.b) + local timeSpanA, _, elementA = self:Compute(element.a) + local timeSpanB, _, elementB = self:Compute(element.b) + local timeSpan = element.timeSpan + timeSpan:Reset() - -- Take intersection of (startA, endA) and (startB, endB) - startA, endA = Intersect(startA, endA, startB, endB) - if Measure(startA, endA) == 0 then - return nil + -- Take intersection of A and B. + Intersect(timeSpanA, timeSpanB, timeSpan) + if Measure(timeSpan) == 0 then + return timeSpan end --[[ @@ -296,11 +319,11 @@ local function ComputeCompare(element) Ovale:Logf("%f+(t-%f)*%f %s %f+(t-%f)*%f [%d]", a, b, c, operator, x, y, z, element.nodeId) --[[ - A(t) = B(t) + A(t) = B(t) a + (t - b)*c = x + (t - y)*z a + t*c - b*c = x + t*z - y*z - t*c - t*z = (x - y*z) - (a - b*c) - t*(c - z) = B(0) - A(0) + t*c - t*z = (x - y*z) - (a - b*c) + t*(c - z) = B(0) - A(0) --]] local A = a - b*c local B = x - y*z @@ -310,37 +333,45 @@ local function ComputeCompare(element) or (operator == "<=" and A <= B) or (operator == ">" and A > B) or (operator == ">=" and A >= B)) then - startA, endA = nil, nil + timeSpan:Reset() end else + local scratch = OvaleTimeSpan(self_pool:Get()) + scratch:Reset(timeSpan) local t = (B - A)/(c - z) if (c > z and operator == "<") or (c > z and operator == "<=") or (c < z and operator == ">") or (c < z and operator == ">=") then - startA, endA = Intersect(startA, endA, 0, t) + IntersectInterval(scratch, 0, t, timeSpan) end if (c < z and operator == "<") or (c < z and operator == "<=") or (c > z and operator == ">") or (c > z and operator == ">=") then - startA, endA = Intersect(startA, endA, t, math.huge) + IntersectInterval(scratch, t, math.huge, timeSpan) end + self_pool:Release(scratch) end - Ovale:Logf("compare %s returns %s, %s [%d]", operator, startA, endA, element.nodeId) - return startA, endA + Ovale:Logf("compare %s returns %s [%d]", operator, tostring(timeSpan), element.nodeId) + return timeSpan end local function ComputeCustomFunction(element) Ovale:Logf("custom function %s", element.name) local self = OvaleBestAction if not element.serial or element.serial < self_serial then - element.startA, element.endA, element.priorityA, element.elementA = self:Compute(element.a) + -- Cache new values in element. + element.timeSpanA, element.priorityA, element.elementA = self:Compute(element.a) element.serial = self_serial else Ovale:Logf("Using cached values for %s", element.name) end - local startA, endA, priorityA, elementA = element.startA, element.endA, element.priorityA, element.elementA + + local timeSpanA, priorityA, elementA = element.timeSpanA, element.priorityA, element.elementA + local timeSpan = element.timeSpan + timeSpan:Reset() + if element.params.asValue and element.params.asValue == 1 then --[[ Allow for the return value of a custom function to be "typecast" to a constant value. @@ -357,7 +388,7 @@ local function ComputeCustomFunction(element) ==]] local atTime = OvaleState.currentTime local value = 0 - if HasTime(startA, endA, atTime) then + if HasTime(timeSpanA, atTime) then if not elementA then -- boolean value = 1 elseif elementA.type == "value" then @@ -366,21 +397,26 @@ local function ComputeCustomFunction(element) value = 1 end end - return 0, math.huge, priorityA, PutValue(element, value, 0, 0) + timeSpan[1], timeSpan[2] = 0, math.huge + return timeSpan, priorityA, PutValue(element, value, 0, 0) else - return startA, endA, priorityA, elementA + CopyTimeSpan(timeSpanA, timeSpan) + return timeSpan, priorityA, elementA end end local function ComputeFunction(element) local self = OvaleBestAction + local timeSpan = element.timeSpan + timeSpan:Reset() + local condition = OvaleCondition.conditions[element.func] if not condition then Ovale:Errorf("Condition %s not found", element.func) - return nil + return timeSpan end - local start, ending, value, origin, rate = condition(element.params) + local start, ending, value, origin, rate = condition(element.params) if Ovale.trace then local conditionCall = element.func .. "(" for k, v in pairs(element.params) do @@ -390,16 +426,21 @@ local function ComputeFunction(element) Ovale:FormatPrint("Condition %s returned %s, %s, %s, %s, %s", conditionCall, start, ending, value, origin, rate) end + if start and ending then + timeSpan[1], timeSpan[2] = start, ending + end if value then - return start, ending, OVALE_DEFAULT_PRIORITY, PutValue(element, value, origin, rate) + return timeSpan, OVALE_DEFAULT_PRIORITY, PutValue(element, value, origin, rate) else - return start, ending + return timeSpan end end local function ComputeGroup(element) local self = OvaleBestAction - local bestStart, bestEnding, bestPriority, bestElement, bestCastTime + local bestTimeSpan, bestPriority, bestElement, bestCastTime + local timeSpan = element.timeSpan + timeSpan:Reset() Ovale:Logf("%s [%d]", element.type, element.nodeId) @@ -407,158 +448,181 @@ local function ComputeGroup(element) return self:Compute(element.nodes[1]) end + local best = OvaleTimeSpan(self_pool:Get()) + local current = OvaleTimeSpan(self_pool:Get()) + for k, v in ipairs(element.nodes) do - local start, ending, priority, newElement = self:Compute(v) + local currentTimeSpan, currentPriority, currentElement = self:Compute(v) -- We only care about actions that are available at time t > OvaleState.currentTime. - start, ending = Intersect(start, ending, OvaleState.currentTime, math.huge) - if Measure(start, ending) > 0 then - local castTime - if newElement then - castTime = newElement.castTime + current:Reset() + IntersectInterval(currentTimeSpan, OvaleState.currentTime, math.huge, current) + if Measure(current) > 0 then + Ovale:Logf(" group checking %s [%d]", tostring(current), element.nodeId) + local currentCastTime + if currentElement then + currentCastTime = currentElement.castTime end - if not castTime or castTime < OvaleState.gcd then - castTime = OvaleState.gcd + if not currentCastTime or currentCastTime < OvaleState.gcd then + currentCastTime = OvaleState.gcd end local replace = false - if not bestStart then + if Measure(best) == 0 then + Ovale:Logf(" group first best %s [%d]", tostring(current), element.nodeId) replace = true - else - if priority and not bestPriority then - Ovale:Errorf("Internal error: bestPriority=nil and priority=%d", priority) - return nil - elseif priority and priority > bestPriority then - -- If the new spell has a higher priority than the best one found, then choose the - -- higher priority spell if its cast is pushed back too far by the lower priority one. - if newElement and newElement.params and newElement.params.wait then - if start - bestStart < newElement.params.wait then - replace = true - end - elseif start - bestStart < bestCastTime * 0.75 then - replace = true - end - elseif priority and priority < bestPriority then - -- If the new spell has a lower priority than the best one found, then choose the - -- lower priority spell only if it doesn't push back the cast of the higher priority - -- one by too much. - if bestElement and bestElement.params and bestElement.params.wait then - if bestStart - start > bestElement.params.wait then - replace = true - end - elseif bestStart - start > castTime * 0.75 then - replace = true - end - else - -- If the spells have the same priority, then pick the one with an earlier cast time. - if bestElement and bestElement.params and bestElement.params.wait then - if bestStart - start > bestElement.params.wait then - replace = true - end - elseif bestStart > start then - replace = true - end + elseif not currentPriority or not bestPriority or currentPriority == bestPriority then + -- If the spells have the same priority, then pick the one with an earlier cast time. + local threshold = (bestElement and bestElement.params) and bestElement.params.wait or 0 + if best[1] - current[1] > threshold then + Ovale:Logf(" group new best %s [%d]", tostring(current), element.nodeId) + replace = true + end + elseif currentPriority > bestPriority then + -- If the current spell has a higher priority than the best one found, then choose the + -- higher priority spell if its cast is pushed back too far by the lower priority one. + local threshold = (currentElement and currentElement.params) and currentElement.params.wait or (bestCastTime * 0.75) + if current[1] - best[1] < threshold then + Ovale:Logf(" group new best (lower prio) %s [%d]", tostring(current), element.nodeId) + replace = true + end + elseif currentPriority < bestPriority then + -- If the current spell has a lower priority than the best one found, then choose the + -- lower priority spell only if it doesn't push back the cast of the higher priority + -- one by too much. + local threshold = (bestElement and bestElement.params) and bestElement.params.wait or (currentCastTime * 0.75) + if best[1] - current[1] > threshold then + Ovale:Logf(" group new best (higher prio) %s [%d]", tostring(current), element.nodeId) + replace = true end end if replace then - bestStart = start - bestPriority = priority - bestElement = newElement - bestEnding = ending - bestCastTime = castTime + best:Reset(current) + bestTimeSpan = currentTimeSpan + bestPriority = currentPriority + bestElement = currentElement + bestCastTime = currentCastTime end + -- If the node is a "wait" node, then skip the remaining nodes. + if currentElement and currentElement.wait then break end end - -- If the node is a "wait" node, then skip the remaining nodes. - if newElement and newElement.wait then break end end - if not bestStart then - Ovale:Logf("group return nil [%d]", element.nodeId) - return nil - end + self_pool:Release(best) + self_pool:Release(current) - if bestElement then - local id = bestElement.value - if bestElement.params then - id = bestElement.params[1] - end - Ovale:Logf("group best action %s remains %f, %f [%d]", id, bestStart, bestEnding, element.nodeId) + if not bestTimeSpan then + Ovale:Logf("group return %s [%d]", tostring(timeSpan), element.nodeId) + return timeSpan else - Ovale:Logf("group no best action returns %f, %f [%d]", bestStart, bestEnding, element.nodeId) + CopyTimeSpan(bestTimeSpan, timeSpan) + if bestElement then + local id = bestElement.value + if bestElement.params then + id = bestElement.params[1] + end + Ovale:Logf("group best action %s remains %s [%d]", id, tostring(timeSpan), element.nodeId) + else + Ovale:Logf("group no best action returns %s [%d]", tostring(timeSpan), element.nodeId) + end + return timeSpan, bestPriority, bestElement end - return bestStart, bestEnding, bestPriority, bestElement end local function ComputeIf(element) Ovale:Logf("%s [%d]", element.type, element.nodeId) local self = OvaleBestAction - local startA, endA = self:ComputeBool(element.a) - local atTime = OvaleState.currentTime - -- "unless A B" is equivalent to "if (not A) B", so take the complement of A. - if element.type == "unless" then - startA, endA = Complement(startA, endA, atTime) + local timeSpanA = self:ComputeBool(element.a) + local timeSpan = element.timeSpan + timeSpan:Reset() + + local conditionTimeSpan = OvaleTimeSpan(self_pool:Get()) + if element.type == "if" then + conditionTimeSpan:Reset(timeSpanA) + elseif element.type == "unless" then + -- "unless A B" is equivalent to "if (not A) B", so take the complement of A. + Complement(timeSpanA, conditionTimeSpan) end - -- Short-circuit evaluation of left argument to AND. - if Measure(startA, endA) == 0 then - Ovale:Logf("%s return timespan with measure 0 [%d]", element.type, element.nodeId) - return nil + -- Short-circuit evaluation of left argument to IF. + if Measure(conditionTimeSpan) == 0 then + timeSpan:Reset(conditionTimeSpan) + self_pool:Release(conditionTimeSpan) + Ovale:Logf("%s return %s [%d]", element.type, tostring(timeSpan), element.nodeId) + return timeSpan, OVALE_DEFAULT_PRIORITY, PutValue(element, 0, 0, 0) end - local startB, endB, priorityB, elementB = self:Compute(element.b) + local timeSpanB, priorityB, elementB = self:Compute(element.b) -- If the "then" clause is a "wait" node, then only wait if the conditions are true. - if elementB and elementB.wait and not HasTime(startA, endA, atTime) then + if elementB and elementB.wait and not HasTime(conditionTimeSpan, OvaleState.currentTime) then elementB.wait = nil end - -- Take intersection of A and B. - startB, endB = Intersect(startA, endA, startB, endB) - if Measure(startB, endB) == 0 then - Ovale:Logf("%s return nil [%d]", element.type, element.nodeId) - else - Ovale:Logf("%s return %f, %f [%d]", element.type, startB, endB, element.nodeId) - end - return startB, endB, priorityB, elementB + -- Take intersection of the condition and B. + Intersect(conditionTimeSpan, timeSpanB, timeSpan) + self_pool:Release(conditionTimeSpan) + + Ovale:Logf("%s return %s [%d]", element.type, tostring(timeSpan), element.nodeId) + return timeSpan, priorityB, elementB end local function ComputeLua(element) local ret = loadstring(element.lua)() Ovale:Logf("lua %s [%d]", ret, element.nodeId) - return 0, math.huge, OVALE_DEFAULT_PRIORITY, PutValue(element, ret, 0, 0) + + local timeSpan = element.timeSpan + timeSpan:Reset() + + timeSpan[1], timeSpan[2] = 0, math.huge + return timeSpan, OVALE_DEFAULT_PRIORITY, PutValue(element, ret, 0, 0) end local function ComputeNot(element) Ovale:Logf("%s [%d]", element.type, element.nodeId) local self = OvaleBestAction - local startA, endA = self:ComputeBool(element.a) - startA, endA = Complement(startA, endA, OvaleState.currentTime) - Ovale:Logf("%s returns %s, %s [%d]", element.type, startA, endA, element.nodeId) - return startA, endA + local timeSpanA = self:ComputeBool(element.a) + local timeSpan = element.timeSpan + timeSpan:Reset() + + Complement(timeSpanA, timeSpan) + Ovale:Logf("%s returns %s [%d]", element.type, tostring(timeSpan), element.nodeId) + return timeSpan end local function ComputeOr(element) Ovale:Logf("%s [%d]", element.type, element.nodeId) local self = OvaleBestAction - local startA, endA = self:ComputeBool(element.a) - local startB, endB = self:ComputeBool(element.b) - -- Take union of (startA, endA) and (startB, endB) - startA, endA = Union(startA, endA, startB, endB) - Ovale:Logf("%s returns %s, %s [%d]", element.type, startA, endA, element.nodeId) - return startA, endA + local timeSpanA = self:ComputeBool(element.a) + local timeSpanB = self:ComputeBool(element.b) + local timeSpan = element.timeSpan + timeSpan:Reset() + + -- Take union of A and B. + Union(timeSpanA, timeSpanB, timeSpan) + Ovale:Logf("%s returns %s [%d]", element.type, tostring(timeSpan), element.nodeId) + return timeSpan end local function ComputeValue(element) Ovale:Logf("value %s", element.value) - return 0, math.huge, OVALE_DEFAULT_PRIORITY, element + local timeSpan = element.timeSpan + timeSpan:Reset() + + timeSpan[1], timeSpan[2] = 0, math.huge + return timeSpan, OVALE_DEFAULT_PRIORITY, element end local function ComputeWait(element) Ovale:Logf("%s [%d]", element.type, element.nodeId) local self = OvaleBestAction - local startA, endA, priorityA, elementA = self:Compute(element.a) + local timeSpanA, priorityA, elementA = self:Compute(element.a) + local timeSpan = element.timeSpan + timeSpan:Reset() + if elementA then elementA.wait = true - Ovale:Logf("%s return %f, %f [%d]", element.type, startA, endA, element.nodeId) + CopyTimeSpan(timeSpanA, timeSpan) + Ovale:Logf("%s return %s [%d]", element.type, tostring(timeSpan), element.nodeId) end - return startA, endA, priorityA, elementA + return timeSpan, priorityA, elementA end -- @@ -594,13 +658,13 @@ function OvaleBestAction:GetActionInfo(element) return nil end - local spellId = element.params[1] local target = element.params.target or OvaleCondition.defaultTarget local action local actionTexture, actionInRange, actionCooldownStart, actionCooldownDuration, actionUsable, actionShortcut, actionIsCurrent, actionEnable if element.func == "spell" then + local spellId = element.params[1] action = OvaleActionBar:GetForSpell(spellId) if not OvaleSpellBook:IsKnownSpell(spellId) and not action then Ovale:Logf("Spell %s not learnt", spellId) @@ -611,6 +675,7 @@ function OvaleBestAction:GetActionInfo(element) actionInRange = API_IsSpellInRange(OvaleSpellBook:GetSpellName(spellId), target) actionCooldownStart, actionCooldownDuration, actionEnable = OvaleState:GetComputedSpellCD(spellId) + -- Verify that the spell may be cast given restrictions specified in SpellInfo(). local si = OvaleData.spellInfo[spellId] if si then if si.stance and not OvaleStance:IsStance(si.stance) then @@ -645,9 +710,10 @@ function OvaleBestAction:GetActionInfo(element) actionUsable = API_IsUsableSpell(spellId) elseif element.func == "macro" then - action = OvaleActionBar:GetForMacro(element.params[1]) + local macro = element.params[1] + action = OvaleActionBar:GetForMacro(macro) if not action then - Ovale:Logf("Unknown macro %s", element.params[1]) + Ovale:Logf("Unknown macro %s", macro) return nil end actionTexture = API_GetActionTexture(action) @@ -675,7 +741,8 @@ function OvaleBestAction:GetActionInfo(element) actionUsable = (spellName ~= nil) elseif element.func == "texture" then - actionTexture = "Interface\\Icons\\" .. element.params[1] + local texture = element.params[1] + actionTexture = "Interface\\Icons\\" .. texture actionInRange = nil actionCooldownStart = OvaleState.maintenant actionCooldownDuration = 0 @@ -712,13 +779,13 @@ function OvaleBestAction:Compute(element) end function OvaleBestAction:ComputeBool(element) - local start, ending, _, newElement = self:Compute(element) + local timeSpan, _, newElement = self:Compute(element) -- Match SimC: 0 is false, non-zero is true. -- (https://code.google.com/p/simulationcraft/wiki/ActionLists#Logical_operators) if newElement and newElement.type == "value" and newElement.value == 0 and newElement.rate == 0 then return nil else - return start, ending + return timeSpan end end -- diff --git a/OvaleCompile.lua b/OvaleCompile.lua index dc51f8f..185fa56 100644 --- a/OvaleCompile.lua +++ b/OvaleCompile.lua @@ -24,6 +24,7 @@ local OvaleScore = Ovale.OvaleScore local OvaleScripts = Ovale.OvaleScripts local OvaleSpellBook = Ovale.OvaleSpellBook local OvaleStance = Ovale.OvaleStance +local OvaleTimeSpan = Ovale.OvaleTimeSpan local ipairs = ipairs local pairs = pairs @@ -42,6 +43,7 @@ local API_GetSpellInfo = GetSpellInfo local self_node = {} local self_pool = OvalePool("OvaleCompile_pool") +local self_timeSpanPool = OvalePool("OvaleCompile_timeSpanPool") local self_defines = {} local self_customFunctions = {} local self_missingSpellList = {} @@ -194,6 +196,7 @@ local function ParseNumber(dummy, value) node.value = tonumber(value) node.origin = 0 node.rate = 0 + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return dummy..AddNode(node) end @@ -240,6 +243,7 @@ local function ParseFunction(prefix, func, params) end node.func = func node.params = paramList + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) local nodeName = AddNode(node) self_functionCalls[func] = node @@ -430,6 +434,7 @@ local function ParseIf(a, b) node.type = "if" node.a = self_node[tonumber(a)] node.b = self_node[tonumber(b)] + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return AddNode(node) end @@ -438,6 +443,7 @@ local function ParseUnless(a, b) node.type = "unless" node.a = self_node[tonumber(a)] node.b = self_node[tonumber(b)] + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return AddNode(node) end @@ -445,6 +451,7 @@ local function ParseWait(a) local node = self_pool:Get() node.type = "wait" node.a = self_node[tonumber(a)] + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return AddNode(node) end @@ -453,6 +460,7 @@ local function ParseAnd(a,b) node.type = "and" node.a = self_node[tonumber(a)] node.b = self_node[tonumber(b)] + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return AddNode(node) end @@ -460,6 +468,7 @@ local function ParseNot(a) local node = self_pool:Get() node.type = "not" node.a = self_node[tonumber(a)] + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return AddNode(node) end @@ -468,6 +477,7 @@ local function ParseOr(a,b) node.type = "or" node.a = self_node[tonumber(a)] node.b = self_node[tonumber(b)] + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return AddNode(node) end @@ -492,6 +502,7 @@ do node.operator = op node.a = self_node[tonumber(a)] node.b = self_node[tonumber(b)] + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return AddNode(node) end end @@ -513,6 +524,7 @@ local function ParseGroup(text) local node = self_pool:Get() node.type = "group" node.nodes = nodes + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return AddNode(node) end @@ -559,6 +571,7 @@ local function ParseLua(text) local node = self_pool:Get() node.type = "lua" node.lua = strsub(text, 2, strlen(text)-1) + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) return AddNode(node) end @@ -604,7 +617,7 @@ local function ParseCommands(text) local nodeId if text then - nodeId = strmatch(text, "node(%d+)") + nodeId = tonumber(strmatch(text, "node(%d+)")) end if not nodeId then Ovale:Print("no master node") @@ -622,29 +635,32 @@ local function ParseCommands(text) end local function ParseAddFunction(name, params, text) - local nodeId = ParseCommands(text) - local node = self_pool:Get() - node.type = "customfunction" - node.name = name - node.params = ParseParameters(params) - node.a = self_node[tonumber(nodeId)] - if not TestConditions(node.params) then - return nil + local paramList = ParseParameters(params) + if TestConditions(paramList) then + local nodeId = ParseCommands(text) + if nodeId then + local node = self_pool:Get() + node.type = "customfunction" + node.name = name + node.params = paramList + node.a = self_node[nodeId] + node.timeSpan = OvaleTimeSpan(self_timeSpanPool:Get()) + return AddNode(node) + end end - return AddNode(node) end local function ParseAddIcon(params, text, secure) - -- On convertit le numéro de node en node - local masterNode = ParseCommands(text) - if not masterNode then return nil end - masterNode = self_node[tonumber(masterNode)] - masterNode.params = ParseParameters(params) - masterNode.secure = secure - if not TestConditions(masterNode.params) then - return nil + local paramList = ParseParameters(params) + if TestConditions(paramList) then + local masterNodeId = ParseCommands(text) + if masterNodeId then + local masterNode = self_node[masterNodeId] + masterNode.params = paramList + masterNode.secure = secure + return masterNode + end end - return masterNode end local function ParseCanStopChannelling(text) @@ -747,6 +763,7 @@ local function CompileScript(text) -- Return all existing nodes to the node pool. for i, node in pairs(self_node) do self_node[i] = nil + self_timeSpanPool:Release(node.timeSpan) self_pool:Release(node) end wipe(self_node) diff --git a/OvaleCondition.lua b/OvaleCondition.lua index cc1886b..250c8e5 100644 --- a/OvaleCondition.lua +++ b/OvaleCondition.lua @@ -30,15 +30,12 @@ local OvaleSpellDamage = Ovale.OvaleSpellDamage local OvaleStance = Ovale.OvaleStance local OvaleState = Ovale.OvaleState local OvaleSwing = Ovale.OvaleSwing -local OvaleTimeSpan = Ovale.OvaleTimeSpan local floor = floor local pairs = pairs local select = select local tostring = tostring local wipe = table.wipe -local Intersect = OvaleTimeSpan.Intersect -local Measure = OvaleTimeSpan.Measure local API_GetBuildInfo = GetBuildInfo local API_GetItemCooldown = GetItemCooldown local API_GetItemCount = GetItemCount @@ -174,7 +171,7 @@ local function TestOvaleValue(start, ending, value, origin, rate, comparator, li if not value or not origin or not rate then return nil elseif not comparator then - if Measure(start, ending) > 0 then + if start < ending then return start, ending, value, origin, rate else return 0, math.huge, 0, 0, 0 @@ -195,12 +192,16 @@ local function TestOvaleValue(start, ending, value, origin, rate, comparator, li or (comparator == "atMost" and rate > 0) or (comparator == "atLeast" and rate < 0) or (comparator == "more" and rate < 0) then - return Intersect(start, ending, 0, (limit - value)/rate + origin) + local t = (limit - value)/rate + origin + ending = (ending < t) and ending or t + return start, ending elseif (comparator == "less" and rate < 0) or (comparator == "atMost" and rate < 0) or (comparator == "atLeast" and rate > 0) or (comparator == "more" and rate > 0) then - return Intersect(start, ending, (limit - value)/rate + origin, math.huge) + local t = (limit - value)/rate + origin + start = (start > t) and start or t + return start, math.huge end return nil end diff --git a/OvaleFrame.lua b/OvaleFrame.lua index 43e03fc..7bc6cdf 100644 --- a/OvaleFrame.lua +++ b/OvaleFrame.lua @@ -26,6 +26,7 @@ do local Version = 7 local pairs = pairs + local tostring = tostring local wipe = table.wipe local API_CreateFrame = CreateFrame local API_GetSpellInfo = GetSpellInfo @@ -182,7 +183,8 @@ do if forceRefresh or Ovale.refreshNeeded[target] or Ovale.refreshNeeded["player"] or Ovale.refreshNeeded["pet"] then Ovale:Logf("****Master Node %d", k) OvaleBestAction:StartNewAction() - local start, ending, priorite, element = OvaleBestAction:Compute(node) + local timeSpan, _, element = OvaleBestAction:Compute(node) + local start = timeSpan[1] if start then Ovale:Logf("Compute start = %f", start) end @@ -273,7 +275,8 @@ do spellTarget = target end OvaleState:ApplySpell(spellId, start, start + castTime, nextCast, false, OvaleGUID:GetGUID(spellTarget)) - start, ending, priorite, element = OvaleBestAction:Compute(node) + timeSpan, _, element = OvaleBestAction:Compute(node) + start = timeSpan[1] icons[2]:Update(element, start, OvaleBestAction:GetActionInfo(element)) else icons[2]:Update(element, nil) diff --git a/OvaleTimeSpan.lua b/OvaleTimeSpan.lua index f52e963..4e78cc4 100644 --- a/OvaleTimeSpan.lua +++ b/OvaleTimeSpan.lua @@ -13,92 +13,698 @@ Infinity is represented by math.huge. Point sets are considered empty. "nil" time spans are considered empty. + + This module supports the following operations on time spans: + Complement + Union (code kindly contributed by Qrux) + Intersection --]] local _, Ovale = ... local OvaleTimeSpan = {} Ovale.OvaleTimeSpan = OvaleTimeSpan +-- +--local debugprint = print +local setmetatable = setmetatable +local tconcat = table.concat +local wipe = table.wipe +-- + +-- +OvaleTimeSpan.__index = OvaleTimeSpan +do + -- Class constructor + setmetatable(OvaleTimeSpan, { __call = function(self, ...) return self:New(...) end }) +end +-- + +-- +local function CompareIntervals(startA, endA, startB, endB) + --debugprint(string.format(" comparing (%s, %s) with (%s, %s)", startA, endA, startB, endB)) + if startA == startB and endA == endB then + -- same (0) + return 0 + elseif startA < startB and endA >= startB and endA <= endB then + -- overlap, A comes-before B (-1) + return -1 + elseif startB < startA and endB >= startA and endB <= endA then + -- overlap, B comes-before A (1) + return 1 + elseif (startA == startB and endA > endB) or (startA < startB and endA == endB) or (startA < startB and endA > endB) then + -- A contains B (-2) + return -2 + elseif (startB == startA and endB > endA) or (startB < startA and endB == endA) or (startB < startA and endB > endA) then + -- B contains A (3) + return 2 + elseif endA <= startB then + -- A before B (-3) + return -3 + elseif endB <= startA then + -- B before A (3) + return 3 + end + -- Fail; unreachable (99) + return 99 +end +-- + -- -function OvaleTimeSpan.Complement(startA, endA, atTime) - --[[ - The complement of an interval is as follows: - - COMPLEMENT{} = (0, math.huge) - COMPLEMENT(0, math.huge) = {} - COMPLEMENT(a, b) = (0, a) UNION (b, math.huge) - - In the second case, it is the union of two intervals. If the point of interest (atTime) - lies in the left interval, then return it. Otherwise, return the right interval. - --]] - if not startA or not endA then - return 0, math.huge - elseif startA == 0 and endA == math.huge then - return nil - elseif 0 <= atTime and atTime < startA then - return 0, startA +function OvaleTimeSpan:New(...) + local A = ... + if type(A) == "table" then + return setmetatable(A, self) else - return endA, math.huge + return setmetatable({ ... }, self) end end -function OvaleTimeSpan.HasTime(start, ending, atTime) - if not start or not ending then - return nil +function OvaleTimeSpan:__tostring() + if not self or #self == 0 then + return "empty set" else - return start <= atTime and atTime <= ending + return tconcat(self, ", ") end end -function OvaleTimeSpan.Intersect(startA, endA, startB, endB) - -- If either (startA, endA) or (startB, endB) are the empty set, then return the empty set. - if not startA or not endA or not startB or not endB then - return nil +function OvaleTimeSpan:Clone() + if not self then + return OvaleTimeSpan() end - -- Swap around so that (startA, endA) comes "before" (startB, endB). - if startA > startB then - startA, startB = startB, startA - endA, endB = endB, endA + return self:CopyTo( {} ) +end + +function OvaleTimeSpan:CopyTo(result) + if not self then + return OvaleTimeSpan(result) end - -- If the two time spans don't overlap, then return the empty set. - -- Otherwise, the take leftmost right endpoint. - if endA <= startB then - return nil - elseif endB < endA then - return startB, endB - else - return startB, endA + for i = 1, #self do + result[i] = self[i] end + return OvaleTimeSpan(result) end -function OvaleTimeSpan.Measure(startA, endA) - if not startA or not endA then - return 0 - elseif startA >= endA then - return 0 - else - return endA - startA +function OvaleTimeSpan:Reset(template) + if self then + wipe(self) + if template then + return template:CopyTo(self) + else + return OvaleTimeSpan(self) + end + end +end + +function OvaleTimeSpan:IsEmpty() + return (not self or #self == 0) +end + +function OvaleTimeSpan:Equals(B) + local A = self + local countA = A and #A or 0 + local countB = B and #B or 0 + + if countA ~= countB then + return false + end + for k = 1, countA do + if A[k] ~= B[k] then + return false + end + end + return true +end + +function OvaleTimeSpan:HasTime(atTime) + local A = self + local countA = A and #A or 0 + for i = 1, countA, 2 do + if A[i] <= atTime and atTime <= A[i+1] then + return true + end end + return false end -function OvaleTimeSpan.Union(startA, endA, startB, endB) - -- TODO: this assumes that (startA, endA) and (startB, endB) overlap. - -- If either (startA, endA) or (startB, endB) are the empty set, then return the other time span. - if not startA or not endA then - return startB, endB - elseif not startB or not endB then - return startA, endA +function OvaleTimeSpan:Measure() + local A = self + local countA = A and #A or 0 + local measure = 0 + for i = 1, countA, 2 do + measure = measure + (A[i+1] - A[i]) end - -- Swap around so that (startA, endA) comes "before" (startB, endB). - if startA > startB then - startA, startB = startB, startA - endA, endB = endB, endA + return measure +end + +function OvaleTimeSpan:Complement(result) + local A = self + local countA = A and #A or 0 + + result = result or {} + + if countA == 0 then + result[1], result[2] = 0, math.huge + return OvaleTimeSpan(result) end - -- Take the rightmost right endpoint. - if endA > endB then - return startA, endA + + local i, k = 1, 1 + + if A[i] == 0 then + i = i + 1 else - return startA, endB + result[k] = 0 + k = k + 1 + end + while i < countA do + result[k] = A[i] + i, k = i + 1, k + 1 + end + if A[i] < math.huge then + result[k], result[k+1] = A[i], math.huge + end + return OvaleTimeSpan(result) +end + +function OvaleTimeSpan:IntersectInterval(startB, endB, result) + local A = self + local countA = A and #A or 0 + result = result or {} + + -- If A is empty, then the intersection is empty. + if countA == 0 or not startB or not endB then + return OvaleTimeSpan(result) + end + + local i, k = 1, 1 + while true do + if i > countA then + break + end + + local startA, endA = A[i], A[i+1] + local compare = CompareIntervals(startA, endA, startB, endB) + if compare == 0 then + -- Same; output, exit. + result[k], result[k+1] = startA, endA + break + elseif compare == -1 then + -- Overlap; A comes before B, output, advance A. + result[k], result[k+1] = startB, endA + i, k = i + 2, k + 2 + elseif compare == 1 then + -- Overlap; B comes before A, output, exit. + result[k], result[k+1] = startA, endB + break + elseif compare == -2 then + -- A contains B; output, exist. + result[k], result[k+1] = startB, endB + break + elseif compare == 2 then + -- B contains A; output, advance A. + result[k], result[k+1] = startA, endA + i, k = i + 2, k + 2 + elseif compare == -3 then + -- A before B + i = i + 2 + elseif compare == 3 then + -- B before A + break + end + end + + return OvaleTimeSpan(result) +end + +function OvaleTimeSpan:Intersect(B, result) + local A = self + local countA = A and #A or 0 + local countB = B and #B or 0 + result = result or {} + + -- If either A or B are empty, then the intersection is empty. + if countA == 0 or countB == 0 then + return OvaleTimeSpan(result) + end + + local i, j, k = 1, 1, 1 + while true do + if i > countA or j > countB then + break + end + + local startA, endA = A[i], A[i+1] + local startB, endB = B[j], B[j+1] + + --debugprint(string.format(" A: (%s, %s)", tostring(startA), tostring(endA))) + --debugprint(string.format(" B: (%s, %s)", tostring(startB), tostring(endB))) + + local compare = CompareIntervals(startA, endA, startB, endB) + --debugprint(" overlap?", compare) + if compare == 0 then + -- Same; output, advance both. + result[k], result[k+1] = startA, endA + i, j, k = i + 2, j + 2, k + 2 + --debugprint(" ADV(A)") + --debugprint(" ADV(B)") + elseif compare == -1 then + -- Overlap; A comes before B, output, advance A. + result[k], result[k+1] = startB, endA + i, k = i + 2, k + 2 + --debugprint(" ADV(A)") + elseif compare == 1 then + -- Overlap; B comes before A, output, advance B. + result[k], result[k+1] = startA, endB + j, k = j + 2, k + 2 + --debugprint(" ADV(B)") + elseif compare == -2 then + -- A contains B; output, advance B. + result[k], result[k+1] = startB, endB + j, k = j + 2, k + 2 + --debugprint(" ADV(B)") + elseif compare == 2 then + -- B contains A; output, advance A. + result[k], result[k+1] = startA, endA + i, k = i + 2, k + 2 + --debugprint(" ADV(A)") + elseif compare == -3 then + -- A before B + i = i + 2 + --debugprint(" ADV(A)") + elseif compare == 3 then + -- B before A + j = j + 2 + --debugprint(" ADV(B)") + else + --debugprint("WTF--can't happen; ABORT NAO!") + i = i + 2 + j = j + 2 + end + end + + return OvaleTimeSpan(result) +end + +function OvaleTimeSpan:Union(B, result) + local A = self + local countA = A and #A or 0 + local countB = B and #B or 0 + result = result or {} + + -- If either A or B are empty, then return the other one. + if countA == 0 and countB == 0 then + return OvaleTimeSpan(result) + elseif countA == 0 then + return not B and OvaleTimeSpan(result) or B:CopyTo(result) + elseif countB == 0 then + return not A and OvaleTimeSpan(result) or A:CopyTo(result) + end + + local i, j, k = 1, 1, 1 + + local startTemp, endTemp = A[i], A[i+1] + + local holdingA = true + local scanningA = false + + while true do + local startA, endA, startB, endB + + if i > countA and j > countB then + -- Write the final temp to output. + result[k], result[k+1] = startTemp, endTemp + break + end + if scanningA and i > countA then + -- Past the end of A; Flip-scan; Flip-hold. + holdingA = not holdingA + scanningA = not scanningA + else + -- Normal; not past the end of A. + startA, endA = A[i], A[i+1] + end + if not scanningA and j > countB then + -- Past the end of B; Flip-scan; Flip-hold. + holdingA = not holdingA + scanningA = not scanningA + else + -- Normal; not past the end of B. + startB, endB = B[j], B[j+1] + end + + local startCurrent = scanningA and startA or startB + local endCurrent = scanningA and endA or endB + + --debugprint(string.format(" temp: (%s, %s)", tostring(startTemp), tostring(endTemp))) + --debugprint(string.format(" A: (%s, %s)", tostring(startA), tostring(endA))) + --debugprint(string.format(" B: (%s, %s)", tostring(startB), tostring(endB))) + --debugprint(string.format("current: (%s, %s)", tostring(startCurrent), tostring(endCurrent))) + --debugprint(" holdA", holdingA) + --debugprint(" scanA", scanningA) + + --[[ + Comparing pairs (temp, current): + + 0 is (2, 3) - (2, 3) (temp equals current) - Advance-scan. + -2 is (1, 5) - (2, 4) (temp contains current) - Advance-scan. + + -1 is (1, 3) - (2, 5) (temp starts-before current) - Update temp-end (to cur2); advance-scan. + 1 is (2, 5) - (1, 3) (current starts-before temp ) - Update temp-start (to cur1); advance-scan. + + 2 is (1, 5) - (2, 4) (current contains temp ) - Reset-temp (to cur); Flip-scan; Flip-hold. + + -3 is (1, 2) - (3, 4) (temp is-before current) - Flip-scan; advance-cur. + 3 is (3, 4) - (1, 2) (current is-before temp ) - Reset-temp (to cur); Flip-scan; Flip-hold. + --]] + + local compare = CompareIntervals(startTemp, endTemp, startCurrent, endCurrent) + --debugprint(" overlap?", compare) + if compare == 0 then + -- Skip. + if scanningA then i = i + 2 else j = j + 2 end + elseif compare == -2 then + -- Simplest cases; advance input-currently-being-scanned. + if scanningA then i = i + 2 else j = j + 2 end + elseif compare == -1 then + -- Update temp-END, advance. + endTemp = endCurrent + if scanningA then i = i + 2 else j = j + 2 end + elseif compare == 1 then + -- update temp-START, advance. + startTemp = startCurrent + if scanningA then i = i + 2 else j = j + 2 end + elseif compare == 2 then + -- We need to flip the side we're scanning (and holding), because the other side contains this side. + startTemp, endTemp = startCurrent, endCurrent + holdingA = not holdingA + scanningA = not scanningA + if scanningA then i = i + 2 else j = j + 2 end + elseif compare == -3 then + -- This (and 3) are the only situations where we capture the output. + --debugprint(" (-3) holdA", holdingA) + --debugprint(" (-3) scanA", scanningA) + if holdingA == scanningA then + result[k], result[k+1] = startTemp, endTemp + startTemp, endTemp = startCurrent, endCurrent + scanningA = not scanningA + k = k + 2 + else + scanningA = not scanningA + if scanningA then + i = i + 2 + --debugprint(" ADV(A)") + else + j = j + 2 + --debugprint(" ADV(B)") + end + end + elseif compare == 3 then + -- This (and -3) are the only situations where we capture the output. + startTemp, endTemp = startCurrent, endCurrent + holdingA = not holdingA + scanningA = not scanningA + else + --debugprint("WTF--can't happen; ABORT NAO!") + i = i + 2 + j = j + 2 + end end + + return OvaleTimeSpan(result) end -- + +do + local testFunction = {} + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 2, 3, 4) + local atTime = 0 + if A:HasTime(atTime) then + print(string.format("%s should not contain %s", tostring(A), tostring(atTime))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 2, 3, 4) + local atTime = 1 + if not A:HasTime(atTime) then + print(string.format("%s should contain %s", tostring(A), tostring(atTime))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 2, 3, 4) + local expected = OvaleTimeSpan(0, 1, 2, 3, 4, math.huge) + local result = A:Complement() + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan() + local expected = OvaleTimeSpan(0, math.huge) + local result = A:Complement() + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(0, math.huge) + local expected = OvaleTimeSpan() + local result = A:Complement() + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 2, 3, 4) + local B = OvaleTimeSpan(2, 3, 4, 5) + local expected = OvaleTimeSpan(1, 5) + + local result = A:Union(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(2, 3, 4, 5) + local B = OvaleTimeSpan(1, 2, 3, 4) + local expected = OvaleTimeSpan(1, 5) + + local result = A:Union(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 2) + local B = OvaleTimeSpan(2, 3, 4, 5) + local expected = OvaleTimeSpan(1, 3, 4, 5) + + local result = A:Union(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(2, 3, 4, 5) + local B = OvaleTimeSpan(1, 2) + local expected = OvaleTimeSpan(1, 3, 4, 5) + + local result = A:Union(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(2, 3, 4, 5) + local B = OvaleTimeSpan(1, 4) + local expected = OvaleTimeSpan(1, 5) + + local result = A:Union(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 5, 6, 10, 15, 17, 20, 30, 99, 101) + local B = OvaleTimeSpan(2, 3, 7, 11, 14, 18, 21, 29, 42, 47, 99, 101) + local expected = OvaleTimeSpan(1, 5, 6, 11, 14, 18, 20, 30, 42, 47, 99, 101) + + local result = A:Union(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 5, 6, 10, 15, 17, 20, 30, 99, 101) + local B = OvaleTimeSpan(2, 3, 7, 11, 14, 18, 21, 29, 42, 47, 101, 105) + local expected = OvaleTimeSpan(1, 5, 6, 11, 14, 18, 20, 30, 42, 47, 99, 105) + + local result = A:Union(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan() + local B = OvaleTimeSpan() + + local a = math.floor(math.random(1, 10)) + local b = math.floor(math.random(1, 10)) + + for i = 1, 20 do + local da = math.floor(math.random(1, 10)) + local db = math.floor(math.random(1, 10)) + A[i], B[i] = a, b + a, b = a + da, b + db + end + + --print(string.format(" A: %s", tostring(A))) + --print(string.format(" B: %s", tostring(B))) + + local result = A:Union(B) + --print(string.format("A union B: %s", tostring(result))) + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan() + local B = OvaleTimeSpan() + local expected = OvaleTimeSpan() + + local result = A:Intersect(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 3) + local B = OvaleTimeSpan(2, 4) + local expected = OvaleTimeSpan(2, 3) + + local result = A:Intersect(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 3, 4, 6) + local B = OvaleTimeSpan(2, 5) + local expected = OvaleTimeSpan(2, 3, 4, 5) + + local result = A:Intersect(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 5, 6, 10, 15, 17, 20, 30, 99, 101) + local B = OvaleTimeSpan(2, 3, 7, 11, 14, 18, 21, 29, 42, 47, 99, 101) + local expected = OvaleTimeSpan(2, 3, 7, 10, 15, 17, 21, 29, 99, 101) + + local result = A:Intersect(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(2, math.huge) + local B = OvaleTimeSpan(3, math.huge) + local expected = OvaleTimeSpan(3, math.huge) + + local result = A:Intersect(B) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + testFunction[#testFunction + 1] = function() + local A = OvaleTimeSpan(1, 3, 4, 6) + local startB, endB = 2, 5 + local expected = OvaleTimeSpan(2, 3, 4, 5) + + local result = A:IntersectInterval(startB, endB) + if not result:Equals(expected) then + print(string.format(" result: %s", tostring(result))) + print(string.format("expected: %s", tostring(expected))) + return false + end + return true + end + + local function TestDriver() + for i, func in ipairs(testFunction) do + local result = func() + local resultString = result and "true" or "FAILED!" + print(string.format("Test %d: %s", i, resultString)) + if not result then + break + end + end + end + --TestDriver() +end diff --git a/compiler.pl b/compiler.pl index 032da09..d7b1cd8 100644 --- a/compiler.pl +++ b/compiler.pl @@ -108,8 +108,10 @@ $sp{OvaleQueue}{NewDeque} = true; $sp{OvaleQueue}{RemoveFront} = true; $sp{OvaleTimeSpan}{Complement} = true; +$sp{OvaleTimeSpan}{CopyTo} = true; $sp{OvaleTimeSpan}{HasTime} = true; $sp{OvaleTimeSpan}{Intersect} = true; +$sp{OvaleTimeSpan}{IntersectInterval} = true; $sp{OvaleTimeSpan}{Measure} = true; $sp{OvaleTimeSpan}{Union} = true; -- 1.7.9.5