Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LibEventSourcing.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<Script file="libs\LibLogger\LibLogger.lua"/>
<Script file="source\Util.lua"/>
<Script file="source\SortedList.lua"/>
<Script file="source\Table.lua"/>
<Script file="source\LogEntry.lua"/>
<Script file="source\IgnoreEntry.lua"/>
<Script file="source\StateManager.lua"/>
Expand Down
15 changes: 12 additions & 3 deletions readme.MD
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,26 @@ Then there is the issue of responding to requests on broadcast channels. We don'
with data...

- When announcing that you are able to send a hash include a timestamp.
- If multiple announcements are sent simultaneously, only the sender with the lowest timestamp (and player name in case of same timestamp)
will start sending data after at 5 seconds.
- If multiple announcements are sent simultaneously, only the sender with the lowest timestamp (and player name in case of same timestamp)
will start sending data after at 5 seconds.



Denial of service
-----------------
The consuming addon is responsible for providing secure communication channels.
Specifically if we can only authorize entries but not sync messages someone can
block sync by rebroadcasting every advertisement with a lower timestamp and then never sending actual data.


# V2

Work has started on V2 for WOTLK.
Some improvements are on the list:
- simplify auth
- allow zero or more handlers for log entries
- allow zero or more handlers for reset
- more efficient serialization

# Contributions

As much as possible is automated when it comes to chores in this library.
Expand Down
43 changes: 25 additions & 18 deletions source/SortedList.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Sorted lists with an insert API
]]--

local SortedList, _ = LibStub:NewLibrary("EventSourcing/SortedList", 1)
local SortedList, _ = LibStub:NewLibrary("EventSourcing/SortedList", 2)
if not SortedList then
return end

Expand Down Expand Up @@ -40,23 +40,28 @@ end
function SortedList:state()
return self._state
end

--[[
Inserts an element into the list, returns the position of the inserted element
]]
function SortedList:insert(element)
if (self._unique) then
error("This list only supports uniqueInsert")
end
self._state = self._state + 1
-- since we expect elements to be mostly appended, we do a shortcut check.
if (#self._entries == 0 or self._compare(self._entries[#self._entries], element) == -1) then
table.insert(self._entries, element)
return
self._entries[#self._entries + 1] = element
return #self._entries
end

local position = Util.BinarySearch(self._entries, element, self._compare)
if position == nil then
table.insert(self._entries, element)
else
if position ~= nil then
table.insert(self._entries, position, element)
return position
end
self._entries[#self._entries + 1] = element
return #self._entries
end

--[[
Expand All @@ -68,13 +73,13 @@ end
function SortedList:uniqueInsert(element)
self._state = self._state + 1
if (#self._entries == 0 or self._compare(self._entries[#self._entries], element) == -1) then
table.insert(self._entries, element)
self._entries[#self._entries + 1] = element
return true
end

local position = Util.BinarySearch(self._entries, element, self._compare)
if position == nil then
table.insert(self._entries, element)
self._entries[#self._entries + 1] = element
elseif self._compare(self._entries[position], element) ~= 0 then
table.insert(self._entries, position, element)
else
Expand All @@ -95,17 +100,19 @@ function SortedList:wipe()
end
self._state = self._state + 1
end
-- We don't return a value since we are change the table, this makes it clear for consuming code
--function SortedList:cast(table, compare)
-- if (table._entries == nil) then
-- error("This is not a sorted list table")
-- end
-- setmetatable(table, SortedList)
-- table._compare = compare
--end



function SortedList:searchGreaterThanOrEqual(entry)
return Util.BinarySearch(self._entries, entry, self._compare)
end

--[[
Remove an element from the list
]]
function SortedList:remove(entry)
local position = self:searchGreaterThanOrEqual(entry)
if position == nil or entry ~= self._entries[position] then
error("Element to remove not found")
end
table.remove(self._entries, position)
return position
end
5 changes: 5 additions & 0 deletions source/StateManager.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ local function hydrateEntryFromList(entry, data)
end

local function trigger(stateManager, event, ...)
local start = GetTimePreciseSec()
for _, callback in ipairs(stateManager.listeners[event] or {}) do
-- trigger callback, pass state manager
local success, result = pcall(callback, stateManager, ...)
Expand All @@ -35,6 +36,10 @@ local function trigger(stateManager, event, ...)
end

end
local duration = GetTimePreciseSec() - start
if (duration > 0.01) then
stateManager.logger:Warning("Event handlers for %s took %f seconds, keep your event handlers below 10ms", event, duration)
end
end

local function entryToList(entry)
Expand Down
175 changes: 175 additions & 0 deletions source/Table.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
local Table, _ = LibStub:NewLibrary("EventSourcing/Table", 1)
if not Table then
return
end

local Util = LibStub("EventSourcing/Util")
local SortedList = LibStub("EventSourcing/SortedList")


local function curryOne(func, param)
return function(...)
return func(param, ...)
end
end


local function addRow(table, row)
-- Add the row to each index
local triggers = {}
table.rowCount = table.rowCount + 1
for indexName, index in pairs(table.indices) do
local position = index:insert(row)
-- Check watches
for _, watch in ipairs(table.watches[indexName]) do
local callback, offset, length = unpack(watch)
if position >= offset and position <= offset + length then
triggers[#triggers + 1] = callback
end
end
end
for indexName, uniqueIndex in pairs(table.uniqueIndices) do
uniqueIndex[indexName] = row
end

-- Execute triggers
for _, callback in ipairs(triggers) do
pcall(callback, 'addRow')
end
end

local function updateRow(table, row, mutator)
-- Get the current location in each index
local oldPositions = {}
for indexName, index in pairs(table.indices) do
oldPositions[indexName] = index:remove(row)
end
mutator()

-- After mutating we insert the row into all indices again
local triggers = {}
for indexName, index in pairs(table.indices) do
local newPosition = index:insert(row)
-- Check watches
for _, watch in ipairs(table.watches[indexName]) do
local callback, offset, length = unpack(watch)
-- trigger for same spot
if (true or oldPositions[indexName] ~= newPosition) and (
(oldPositions[indexName] >= offset and oldPositions[indexName] <= offset + length)
or (newPosition >= offset and newPosition <= offset + length)
) then
triggers[#triggers + 1] = callback
end
end
end

-- Execute triggers
for _, callback in ipairs(triggers) do
pcall(callback, 'updateRow')
end
end

local function iterateByIndex(table, name, start, length)
local i = (start or 1) - 1
local data = table.indices[name]:entries()
local n = math.min(#data, i + (length or 0))
return function()
i = i + 1
if i <=n then return i, data[i] end
end
end

local function retrieveByUniqueIndex(table, indexName, key)
return table.uniqueIndices[indexName][key]
end

--[[
Register a callback to be called when a specific part of the index changes
Returns a function that can be used to remove the watch and a function to update the offset / length.
]]
local function watchIndexRange(table, indexName, callback, offset, length)
if table.indices[indexName] == nil then
error(string.format("Attempt to watch unknown sorted index %s"))
end
local watches = table.watches[indexName]
local watch = {nil, offset, length}
local paused = false
-- We want to pass an iterator to the callback, so we curry it.
watch[1] = function(reason)
return paused or callback(iterateByIndex(table, indexName, watch[2], watch[3]), reason, table.rowCount)
end
watches[#watches+1] = watch

local updateOffset = function(newOffset)
watch[2] = newOffset
watch[1]('updateOffset')
end

local update = function(newOffset, newLength)
watch[2] = newOffset
watch[3] = newLength
watch[1]('updateWatch')
end

local cancel = function()
for i, v in ipairs(watches) do
if v == watch then
watches[i] = nil
break;
end
end
end

return {
update = update,
updateOffset = updateOffset,
cancel = cancel,
pause = function() paused = true end,
resume = function()
paused = false
watch[1]('resume')

end,
trigger = function() watch[1]('trigger') end
}
end


-- unique indices are implemented as dictionaries
Table.new = function(uniqueIndices, indices)
local private = {
-- A list of index data tables
indices = {},
watches = {},
-- store data
uniqueIndices = {},
-- store retriever function
uniqueIndexers = {},
rowCount = 0
}

for name, compare in pairs(indices or {}) do
Util.assertFunction(compare)
private.indices[name] = SortedList:new({}, compare, false)
private.watches[name] = {}
end
for name, indexer in pairs(uniqueIndices or {}) do
Util.assertFunction(indexer)
private.uniqueIndices[name] = {}
private.uniqueIndexers[name] = indexer
private.watches[name] = {}
end
local public = {
addRow = curryOne(addRow, private),
updateRow = curryOne(updateRow, private),
iterateByIndex = curryOne(iterateByIndex, private),
watchIndexRange = curryOne(watchIndexRange, private),
retrieveByUniqueIndex = curryOne(retrieveByUniqueIndex, private)

}

return public
end



30 changes: 26 additions & 4 deletions source/Util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ if not Util then
return end
-- Searches for the first index that has value >= to the given value
function Util.BinarySearch(list, value, comparator, min, max)
if type(list) ~= 'table' then
error("Argument 1 must be a table")
end

Util.assertTable(list, 'list')
if min == nil then
min = 1
max = #list
Expand All @@ -21,6 +18,31 @@ function Util.BinarySearch(list, value, comparator, min, max)

local result = comparator(list[test], value)

if result == 0 then
if (list[test] == value) then
return test
end
-- Linear search left side
local linearTest = test
while linearTest > 1 and result == 0 do
linearTest = linearTest - 1
result = comparator(list[linearTest], value)
if list[linearTest] == value then
return linearTest
end
end

linearTest = test
result = 0
while linearTest < #list and result == 0 do
linearTest = linearTest + 1
result = comparator(list[linearTest], value)
if list[linearTest] == value then
return linearTest
end
end
end



if result == -1 then
Expand Down
Loading