-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mock libwho and test scan. use left most binary search to find leader in `O(log n + 1)`.
- Loading branch information
1 parent
b4c0213
commit d0bfa97
Showing
16 changed files
with
571 additions
and
212 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
-- Addon global | ||
local TheClassicRace = _G.TheClassicRace | ||
|
||
--[[ | ||
TheClassicRaceScan does a left-most binary search | ||
to find the lower bound level in our /who query which gives > 0 results but < 50 (because we only get 50 from 1 query) | ||
]]-- | ||
---@class TheClassicRaceScan | ||
---@field DB table<string, table> | ||
---@field Core TheClassicRaceCore | ||
---@field EventBus TheClassicRaceEventBus | ||
local TheClassicRaceScan = {} | ||
TheClassicRaceScan.__index = TheClassicRaceScan | ||
TheClassicRace.Scan = TheClassicRaceScan | ||
setmetatable(TheClassicRaceScan, { | ||
__call = function(cls, ...) | ||
return cls.new(...) | ||
end, | ||
}) | ||
|
||
function TheClassicRaceScan.new(Core, DB, EventBus, who, min, max) | ||
local self = setmetatable({}, TheClassicRaceScan) | ||
|
||
self.Core = Core | ||
self.DB = DB | ||
self.EventBus = EventBus | ||
self.who = who | ||
|
||
self.min = min | ||
self.max = max | ||
|
||
self.done = false | ||
self.started = false | ||
|
||
return self | ||
end | ||
|
||
function TheClassicRaceScan:SetMin(min) | ||
-- normally we'd err but can't in WoW addons? | ||
if self.started then | ||
return | ||
end | ||
|
||
self.min = min | ||
end | ||
|
||
function TheClassicRaceScan:IsDone() | ||
return self.done | ||
end | ||
|
||
function TheClassicRaceScan:HandleResult(_, result, complete) | ||
-- if we have 0 results then we need to decrease lower bound | ||
if #result == 0 then | ||
-- value > target -> right = m | ||
self.right = self.m | ||
|
||
-- if we have > 0 results and not more than we can query in 1 /who then we are done | ||
elseif complete then | ||
-- value == target -> right = m | ||
-- we can exit early here because we don't need to find the exact m | ||
self.done = true | ||
return | ||
|
||
-- if we have too many results for 1 /who query then we need to increase lower bound to refine the result | ||
else | ||
-- value < target -> left = m + 1 | ||
|
||
-- too many people at highest level, we can exit early | ||
if self.m == self.max then | ||
self.done = true | ||
return | ||
end | ||
|
||
self.left = self.m + 1 | ||
end | ||
|
||
-- do next scan | ||
self:Next() | ||
end | ||
|
||
function TheClassicRaceScan:Start() | ||
if self.started then | ||
return | ||
end | ||
|
||
self.started = true | ||
|
||
-- instead of starting with our binary search we start with a shortcut to search for (min, max) | ||
-- and then lead into the binary search | ||
self:Shortcut() | ||
end | ||
|
||
function TheClassicRaceScan:Shortcut() | ||
local function cb(query, result, complete) | ||
TheClassicRace:DebugPrint("who '" .. query .. "' result: " .. #result .. ", complete: " .. tostring(complete)) | ||
|
||
if complete and #result > 0 then | ||
for _, player in ipairs(result) do | ||
TheClassicRace:DebugPrint(" - " .. player.Name .. " lvl" .. player.Level) | ||
end | ||
else | ||
self:BinarySearch() | ||
end | ||
end | ||
|
||
self.who(self.min, self.max, cb) | ||
end | ||
|
||
function TheClassicRaceScan:BinarySearch() | ||
-- binary search state | ||
self.left = self.min | ||
self.right = self.max | ||
|
||
self:Next() | ||
end | ||
|
||
function TheClassicRaceScan:Next() | ||
self.m = math.floor((self.left + self.right) / 2) | ||
|
||
local function cb(query, result, complete) | ||
TheClassicRace:DebugPrint("who '" .. query .. "' result: " .. #result .. ", complete: " .. tostring(complete)) | ||
|
||
if complete and #result > 0 then | ||
for _, player in ipairs(result) do | ||
TheClassicRace:DebugPrint(" - " .. player.Name .. " lvl" .. player.Level) | ||
end | ||
end | ||
|
||
self:HandleResult(query, result, complete) | ||
end | ||
|
||
self.who(self.m, self.max, cb) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
-- Addon global | ||
local TheClassicRace = _G.TheClassicRace | ||
|
||
-- WoW API | ||
local CreateFrame = _G.CreateFrame | ||
|
||
---@class TheClassicRaceTracker | ||
---@field DB table<string, table> | ||
---@field Core TheClassicRaceCore | ||
---@field EventBus TheClassicRaceEventBus | ||
---@field Network TheClassicRaceNetwork | ||
local TheClassicRaceTracker = {} | ||
TheClassicRaceTracker.__index = TheClassicRaceTracker | ||
TheClassicRace.Tracker = TheClassicRaceTracker | ||
setmetatable(TheClassicRaceTracker, { | ||
__call = function(cls, ...) | ||
return cls.new(...) | ||
end, | ||
}) | ||
|
||
function TheClassicRaceTracker.new(Core, DB, EventBus, Network) | ||
local self = setmetatable({}, TheClassicRaceTracker) | ||
|
||
self.Core = Core | ||
self.DB = DB | ||
self.EventBus = EventBus | ||
self.Network = Network | ||
|
||
-- @TODO: do we need the frame? | ||
self.Frame = CreateFrame("Frame") | ||
self.Frame:SetScript("OnEvent", function() | ||
end) | ||
|
||
-- subscribe to network events | ||
EventBus:RegisterCallback(TheClassicRace.Config.Network.Events.Ding, self, self.OnDing) | ||
EventBus:RegisterCallback(TheClassicRace.Config.Network.Events.RequestUpdate, self, self.OnRequestUpdate) | ||
-- subscribe to local events | ||
EventBus:RegisterCallback(TheClassicRace.Config.Events.PlayerInfo, self, self.OnPlayerInfo) | ||
|
||
return self | ||
end | ||
|
||
function TheClassicRaceTracker:RequestUpdate() | ||
self.Network:SendObject(TheClassicRace.Config.Network.Events.RequestUpdate, {}, "CHANNEL") | ||
end | ||
|
||
function TheClassicRaceTracker:OnRequestUpdate(_, sender) | ||
TheClassicRace:DebugPrint("Update Requested") | ||
-- if we don't know a leader yet then we can't respond | ||
if self.DB.realm.leader == nil then | ||
TheClassicRace:DebugPrint("Update Requested, but no leader") | ||
return | ||
end | ||
|
||
-- respond with leader | ||
self.Network:SendObject(TheClassicRace.Config.Network.Events.Ding, self.DB.realm.leader, "WHISPER", sender) | ||
end | ||
|
||
function TheClassicRaceTracker:OnDing(playerInfo) | ||
self:HandlePlayerInfo({ | ||
Name = playerInfo[1], | ||
Level = playerInfo[2], | ||
DingedAt = playerInfo[3], | ||
}, false) | ||
end | ||
|
||
function TheClassicRaceTracker:OnPlayerInfo(playerInfo) | ||
self:HandlePlayerInfo(playerInfo, true) | ||
end | ||
|
||
function TheClassicRaceTracker:OnMaxLevelBump() | ||
-- we only care about >= highest level - 10 | ||
self.DB.realm.levelThreshold = math.max( | ||
self.DB.realm.levelThreshold, | ||
self.DB.realm.highestLevel - 10 | ||
) | ||
|
||
-- clean our DB of lower level records | ||
for playerName, playerInfo in pairs(self.DB.realm.players) do | ||
if playerInfo.level < self.DB.realm.levelThreshold then | ||
self.DB.realm.players[playerName] = nil | ||
end | ||
end | ||
end | ||
|
||
function TheClassicRaceTracker:HandlePlayerInfo(playerInfo, shouldBroadcast) | ||
TheClassicRace:DebugPrint("HandlePlayerInfo: " .. playerInfo.Name .. " lvl" .. playerInfo.Level) | ||
-- ignore players below our lower bound threshold | ||
if playerInfo.Level < self.DB.realm.levelThreshold then | ||
TheClassicRace:DebugPrint("Ignored player info < lvl" .. self.DB.realm.levelThreshold) | ||
return | ||
end | ||
|
||
local now = self.Core:Now() | ||
local dingedAt = playerInfo.DingedAt | ||
if dingedAt == nil then | ||
dingedAt = now | ||
end | ||
local isNew = self.DB.realm.players[playerInfo.Name].level == nil | ||
local isDing = not isNew and playerInfo.Level > self.DB.realm.players[playerInfo.Name].level | ||
|
||
-- store player info | ||
self.DB.realm.players[playerInfo.Name].level = playerInfo.Level | ||
self.DB.realm.players[playerInfo.Name].lastseenAt = now | ||
if isDing or isNew then | ||
self.DB.realm.players[playerInfo.Name].dingedAt = dingedAt | ||
|
||
-- broadcast new/ding | ||
if shouldBroadcast then | ||
self.Network:SendObject(TheClassicRace.Config.Network.Events.Ding, | ||
{ playerInfo.Name, playerInfo.Level, dingedAt }, "CHANNEL") | ||
end | ||
end | ||
|
||
-- new highest level! implies ding | ||
if playerInfo.Level > self.DB.realm.highestLevel then | ||
self.DB.realm.highestLevel = playerInfo.Level | ||
self.DB.realm.leader = { playerInfo.Name, playerInfo.Level, dingedAt } | ||
|
||
if playerInfo.Level == TheClassicRace.Config.MaxLevel then | ||
TheClassicRace:PPrint("The race is over! Gratz to " .. playerInfo.Name .. ", first to reach max level!!") | ||
else | ||
TheClassicRace:PPrint("Gratz to " .. TheClassicRace:PlayerChatLink(playerInfo.Name) .. ", first to reach level " .. playerInfo.Level .. "!") | ||
end | ||
|
||
self:OnMaxLevelBump() | ||
elseif isDing then | ||
TheClassicRace:PPrint("Gratz to " .. playerInfo.Name .. ", reached level " .. playerInfo.Level .. "!") | ||
end | ||
end |
Oops, something went wrong.