Johnny C. Lam [10-31-13 - 19:06]
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")
--</private-static-properties>
--<private-static-methods>
@@ -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
--</private-static-methods>
@@ -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
--</public-static-methods>
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
+--<private-static-properties>
+--local debugprint = print
+local setmetatable = setmetatable
+local tconcat = table.concat
+local wipe = table.wipe
+--</private-static-properties>
+
+--<public-static-properties>
+OvaleTimeSpan.__index = OvaleTimeSpan
+do
+ -- Class constructor
+ setmetatable(OvaleTimeSpan, { __call = function(self, ...) return self:New(...) end })
+end
+--</public-static-properties>
+
+--<private-static-methods>
+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
+--</private-static-methods>
+
--<public-static-methods>
-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
--</public-static-methods>
+
+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;