mod: entity prototype improvements and refactoring

- refactor and move queue & emitter classes for consistency
- don't emit all prototypes at once
- add simple queue for 'instant' packets so the printer doesn't get overwhelmed and mix up the output
This commit is contained in:
Adam 2022-07-09 14:03:57 +02:00
parent ae0eb98f12
commit 90aa086dd4
33 changed files with 742 additions and 652 deletions

View file

@ -1,144 +0,0 @@
local ____lualib = require("lualib_bundle")
local __TS__Class = ____lualib.__TS__Class
local __TS__New = ____lualib.__TS__New
local ____exports = {}
local ____KafkatorioSettings = require("main.settings.KafkatorioSettings")
local KafkatorioSettings = ____KafkatorioSettings.default
local ____PacketEmitter = require("main.PacketEmitter")
local packetEmitter = ____PacketEmitter.default
____exports.EventUpdates = {}
local EventUpdates = ____exports.EventUpdates
do
local CacheEntry
EventUpdates.Manager = __TS__Class()
local Manager = EventUpdates.Manager
Manager.name = "Manager"
function Manager.prototype.____constructor(self)
end
function Manager.prototype.init(self, force)
local isAnythingUndefined = global.cache == nil
log(((("Initialising EventDataCache (force=" .. tostring(force)) .. ", isAnythingUndefined=") .. tostring(isAnythingUndefined)) .. ")...")
if force == true or isAnythingUndefined then
log("Initialising global.cache")
global.cache = {}
end
log("Finished initialising EventDataCache")
end
function Manager.prototype.throttle(self, key, ____type, mutate, expirationDurationTicks)
self:update(
key,
____type,
mutate,
false,
expirationDurationTicks
)
end
function Manager.prototype.debounce(self, key, ____type, mutate, expirationDurationTicks)
self:update(
key,
____type,
mutate,
true,
expirationDurationTicks
)
end
function Manager.prototype.update(self, key, ____type, mutate, resetLastUpdated, expirationDurationTicks)
local entry = self:getCacheEntry(key, ____type) or EventUpdates.Manager:createCacheEntry(key, ____type)
mutate(entry.packet)
if resetLastUpdated then
entry.lastUpdatedTick = game.tick
end
if expirationDurationTicks ~= nil then
entry.expirationDurationTicks = expirationDurationTicks
end
if entry.expirationDurationTicks ~= nil and entry.expirationDurationTicks <= 0 then
self:extractAndEmitExpiredPackets()
end
end
function Manager.prototype.setExpiration(self, key, ____type, expirationDurationTicks)
local entry = self:getCacheEntry(key, ____type)
if entry ~= nil then
entry.expirationDurationTicks = expirationDurationTicks
end
end
function Manager.prototype.extractExpired(self)
local countExpired = 0
local countTotal = 0
local expiredData = {}
for key, entry in pairs(global.cache) do
countTotal = countTotal + 1
if self:isExpired(entry) then
countExpired = countExpired + 1
expiredData[#expiredData + 1] = entry.packet
global.cache[key] = nil
end
end
local hasExpiredItems = countExpired > 0
local hasAnyItem = countTotal > 0
if hasExpiredItems or game.tick % 30 == 0 and hasAnyItem then
log((("[extractExpired] expired items: " .. tostring(countExpired)) .. ", total: ") .. tostring(countTotal))
end
return expiredData
end
function Manager.prototype.extractAndEmitExpiredPackets(self)
local cachedEvents = self:extractExpired()
if #cachedEvents > 0 then
log((("[EventDataCache] emitting " .. tostring(#cachedEvents)) .. " events on tick ") .. tostring(game.tick))
for ____, event in ipairs(cachedEvents) do
packetEmitter:emitKeyedPacket(event)
end
end
return #cachedEvents
end
function Manager.prototype.getCacheEntry(self, key, ____type)
local hash = EventUpdates.Manager:hashKey(key)
if not (global.cache[hash] ~= nil) then
return nil
end
local value = global.cache[hash]
if self:isEntryInstanceOf(value, ____type) then
return value
else
return nil
end
end
function Manager.createCacheEntry(self, key, ____type)
local data = {type = ____type, key = key}
local entry = __TS__New(CacheEntry, data)
local hash = EventUpdates.Manager:hashKey(key)
global.cache[hash] = entry
return entry
end
function Manager.prototype.isExpired(self, entry)
local expiryDuration = entry.expirationDurationTicks or KafkatorioSettings:getEventCacheExpirationTicks(entry.packet)
if expiryDuration == nil then
return true
else
return game.tick - entry.lastUpdatedTick > expiryDuration
end
end
function Manager.prototype.isEntryInstanceOf(self, entry, ____type)
local ____self_isDataInstanceOf_2 = self.isDataInstanceOf
local ____entry_packet_0 = entry
if ____entry_packet_0 ~= nil then
____entry_packet_0 = ____entry_packet_0.packet
end
return ____self_isDataInstanceOf_2(self, ____entry_packet_0, ____type)
end
function Manager.prototype.isDataInstanceOf(self, packet, ____type)
return packet ~= nil and packet.type == ____type
end
function Manager.hashKey(self, key)
return game.encode_string(game.table_to_json(key))
end
CacheEntry = __TS__Class()
CacheEntry.name = "CacheEntry"
function CacheEntry.prototype.____constructor(self, packet, expirationDurationTicks)
self.lastUpdatedTick = game.tick
self.packet = packet
self.expirationDurationTicks = expirationDurationTicks
end
end
local EventUpdatesManager = __TS__New(____exports.EventUpdates.Manager)
____exports.default = EventUpdatesManager
return ____exports

View file

@ -5,10 +5,10 @@ local __TS__ParseInt = ____lualib.__TS__ParseInt
local ____exports = {} local ____exports = {}
local ____global_2Dinit = require("main.global-init") local ____global_2Dinit = require("main.global-init")
local initGlobal = ____global_2Dinit.initGlobal local initGlobal = ____global_2Dinit.initGlobal
local ____queue = require("main.queue.queue")
local Queue = ____queue.Queue
local ____prototypeUpdates = require("main.events.prototypeUpdates") local ____prototypeUpdates = require("main.events.prototypeUpdates")
local emitPrototypes = ____prototypeUpdates.emitPrototypes local emitPrototypes = ____prototypeUpdates.emitPrototypes
local ____queue = require("main.queue.queue")
local EventDataQueue = ____queue.default
commands.add_command( commands.add_command(
"kafkatorio", "kafkatorio",
"kafkatorio innit bruv", "kafkatorio innit bruv",
@ -56,7 +56,7 @@ commands.add_command(
surface = surface, surface = surface,
tick = e.tick tick = e.tick
} }
Queue.enqueue( EventDataQueue:enqueue(
(tostring(surface.index) .. tostring(chunk.x)) .. tostring(chunk.y), (tostring(surface.index) .. tostring(chunk.x)) .. tostring(chunk.y),
data, data,
50 50

View file

@ -0,0 +1,141 @@
local ____lualib = require("lualib_bundle")
local __TS__Class = ____lualib.__TS__Class
local __TS__New = ____lualib.__TS__New
local ____exports = {}
local CacheEntry
local ____KafkatorioSettings = require("main.settings.KafkatorioSettings")
local KafkatorioSettings = ____KafkatorioSettings.default
local ____PacketEmitter = require("main.emitting.PacketEmitter")
local PacketEmitter = ____PacketEmitter.default
____exports.EventUpdatesManager = __TS__Class()
local EventUpdatesManager = ____exports.EventUpdatesManager
EventUpdatesManager.name = "EventUpdatesManager"
function EventUpdatesManager.prototype.____constructor(self)
self:init()
end
function EventUpdatesManager.prototype.init(self, force)
local isAnythingUndefined = global.eventUpdatesManagerCache == nil
log(((("Initialising EventDataCache (force=" .. tostring(force)) .. ", isAnythingUndefined=") .. tostring(isAnythingUndefined)) .. ")...")
if force == true or isAnythingUndefined then
log("Initialising global.cache")
global.eventUpdatesManagerCache = {}
end
log("Finished initialising EventDataCache")
end
function EventUpdatesManager.prototype.throttle(self, key, ____type, mutate, expirationDurationTicks)
self:update(
key,
____type,
mutate,
false,
expirationDurationTicks
)
end
function EventUpdatesManager.prototype.debounce(self, key, ____type, mutate, expirationDurationTicks)
self:update(
key,
____type,
mutate,
true,
expirationDurationTicks
)
end
function EventUpdatesManager.prototype.update(self, key, ____type, mutate, resetLastUpdated, expirationDurationTicks)
local entry = self:getCacheEntry(key, ____type) or ____exports.EventUpdatesManager:createCacheEntry(key, ____type)
mutate(entry.packet)
if resetLastUpdated then
entry.lastUpdatedTick = game.tick
end
if expirationDurationTicks ~= nil then
entry.expirationDurationTicks = expirationDurationTicks
end
if entry.expirationDurationTicks ~= nil and entry.expirationDurationTicks <= 0 then
self:extractAndEmitExpiredPackets()
end
end
function EventUpdatesManager.prototype.setExpiration(self, key, ____type, expirationDurationTicks)
local entry = self:getCacheEntry(key, ____type)
if entry ~= nil then
entry.expirationDurationTicks = expirationDurationTicks
end
end
function EventUpdatesManager.prototype.extractExpired(self)
local countExpired = 0
local countTotal = 0
local expiredData = {}
for key, entry in pairs(global.eventUpdatesManagerCache) do
countTotal = countTotal + 1
if self:isExpired(entry) then
countExpired = countExpired + 1
expiredData[#expiredData + 1] = entry.packet
global.eventUpdatesManagerCache[key] = nil
end
end
local hasExpiredItems = countExpired > 0
local hasAnyItem = countTotal > 0
if hasExpiredItems or game.tick % 30 == 0 and hasAnyItem then
log((("[extractExpired] expired items: " .. tostring(countExpired)) .. ", total: ") .. tostring(countTotal))
end
return expiredData
end
function EventUpdatesManager.prototype.extractAndEmitExpiredPackets(self)
local cachedEvents = self:extractExpired()
if #cachedEvents > 0 then
log((("[EventDataCache] emitting " .. tostring(#cachedEvents)) .. " events on tick ") .. tostring(game.tick))
for ____, event in ipairs(cachedEvents) do
PacketEmitter:emitKeyedPacket(event)
end
end
return #cachedEvents
end
function EventUpdatesManager.prototype.getCacheEntry(self, key, ____type)
local hash = ____exports.EventUpdatesManager:hashKey(key)
if not (global.eventUpdatesManagerCache[hash] ~= nil) then
return nil
end
local value = global.eventUpdatesManagerCache[hash]
if self:isEntryInstanceOf(value, ____type) then
return value
else
return nil
end
end
function EventUpdatesManager.createCacheEntry(self, key, ____type)
local data = {type = ____type, key = key}
local entry = __TS__New(CacheEntry, data)
local hash = ____exports.EventUpdatesManager:hashKey(key)
global.eventUpdatesManagerCache[hash] = entry
return entry
end
function EventUpdatesManager.prototype.isExpired(self, entry)
local expiryDuration = entry.expirationDurationTicks or KafkatorioSettings:getEventCacheExpirationTicks(entry.packet)
if expiryDuration == nil then
return true
else
return game.tick - entry.lastUpdatedTick > expiryDuration
end
end
function EventUpdatesManager.prototype.isEntryInstanceOf(self, entry, ____type)
local ____self_isDataInstanceOf_2 = self.isDataInstanceOf
local ____entry_packet_0 = entry
if ____entry_packet_0 ~= nil then
____entry_packet_0 = ____entry_packet_0.packet
end
return ____self_isDataInstanceOf_2(self, ____entry_packet_0, ____type)
end
function EventUpdatesManager.prototype.isDataInstanceOf(self, packet, ____type)
return packet ~= nil and packet.type == ____type
end
function EventUpdatesManager.hashKey(self, key)
return game.encode_string(game.table_to_json(key))
end
local EventUpdates = __TS__New(____exports.EventUpdatesManager)
____exports.default = EventUpdates
CacheEntry = __TS__Class()
CacheEntry.name = "CacheEntry"
function CacheEntry.prototype.____constructor(self, packet, expirationDurationTicks)
self.lastUpdatedTick = game.tick
self.packet = packet
self.expirationDurationTicks = expirationDurationTicks
end
return ____exports

View file

@ -0,0 +1,44 @@
local ____lualib = require("lualib_bundle")
local __TS__Class = ____lualib.__TS__Class
local __TS__SetDescriptor = ____lualib.__TS__SetDescriptor
local __TS__New = ____lualib.__TS__New
local ____exports = {}
____exports.KafkatorioPacketQueueManager = __TS__Class()
local KafkatorioPacketQueueManager = ____exports.KafkatorioPacketQueueManager
KafkatorioPacketQueueManager.name = "KafkatorioPacketQueueManager"
function KafkatorioPacketQueueManager.prototype.____constructor(self)
self.size = 0
self.first = nil
self.last = nil
end
__TS__SetDescriptor(
KafkatorioPacketQueueManager.prototype,
"length",
{get = function(self)
return self.size
end},
true
)
function KafkatorioPacketQueueManager.prototype.enqueue(self, packet)
local last = self.last
self.last = {value = packet}
if last ~= nil then
last.next = self.last
end
if self.first == nil then
self.first = self.last
end
self.size = self.size + 1
end
function KafkatorioPacketQueueManager.prototype.dequeue(self)
local first = self.first
if first == nil then
return nil
end
self.first = first.next or nil
self.size = self.size - 1
return first.value
end
local KafkatorioPacketQueue = __TS__New(____exports.KafkatorioPacketQueueManager)
____exports.default = KafkatorioPacketQueue
return ____exports

View file

@ -1,21 +1,20 @@
local ____exports = {} local ____exports = {}
local ____PacketEmitter = require("main.PacketEmitter")
local PacketEmitter = ____PacketEmitter.default
local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema") local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema")
local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData
local Type = KafkatorioPacketData.Type local ____KafkatorioPacketQueue = require("main.emitting.KafkatorioPacketQueue")
local KafkatorioPacketQueue = ____KafkatorioPacketQueue.default
script.on_event( script.on_event(
defines.events.on_console_chat, defines.events.on_console_chat,
function(event) function(event)
local update = {type = Type.ConsoleChatUpdate, authorPlayerIndex = event.player_index or nil, content = event.message} local update = {type = KafkatorioPacketData.Type.ConsoleChatUpdate, authorPlayerIndex = event.player_index or nil, content = event.message}
PacketEmitter:emitInstantPacket(update) KafkatorioPacketQueue:enqueue(update)
end end
) )
script.on_event( script.on_event(
defines.events.on_console_command, defines.events.on_console_command,
function(event) function(event)
local update = {type = Type.ConsoleCommandUpdate, authorPlayerIndex = event.player_index or nil, command = event.command, parameters = event.parameters} local update = {type = KafkatorioPacketData.Type.ConsoleCommandUpdate, authorPlayerIndex = event.player_index or nil, command = event.command, parameters = event.parameters}
PacketEmitter:emitInstantPacket(update) KafkatorioPacketQueue:enqueue(update)
end end
) )
return ____exports return ____exports

View file

@ -9,8 +9,8 @@ local __TS__ArrayMap = ____lualib.__TS__ArrayMap
local ____exports = {} local ____exports = {}
local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema") local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema")
local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData
local ____PacketEmitter = require("main.PacketEmitter") local ____KafkatorioPacketQueue = require("main.emitting.KafkatorioPacketQueue")
local PacketEmitter = ____PacketEmitter.default local KafkatorioPacketQueue = ____KafkatorioPacketQueue.default
____exports.ConfigurationUpdateHandler = __TS__Class() ____exports.ConfigurationUpdateHandler = __TS__Class()
local ConfigurationUpdateHandler = ____exports.ConfigurationUpdateHandler local ConfigurationUpdateHandler = ____exports.ConfigurationUpdateHandler
ConfigurationUpdateHandler.name = "ConfigurationUpdateHandler" ConfigurationUpdateHandler.name = "ConfigurationUpdateHandler"
@ -24,7 +24,7 @@ function ConfigurationUpdateHandler.prototype.emitConfigurationUpdate(self, chan
allMods = ____exports.ConfigurationUpdateHandler:allMods(changeData), allMods = ____exports.ConfigurationUpdateHandler:allMods(changeData),
factorioData = ____exports.ConfigurationUpdateHandler:factorioData(changeData) factorioData = ____exports.ConfigurationUpdateHandler:factorioData(changeData)
} }
PacketEmitter:emitInstantPacket(configUpdateData) KafkatorioPacketQueue:enqueue(configUpdateData)
end end
function ConfigurationUpdateHandler.factorioData(self, changeData) function ConfigurationUpdateHandler.factorioData(self, changeData)
return {oldVersion = changeData.old_version or nil, newVersion = changeData.new_version or nil} return {oldVersion = changeData.old_version or nil, newVersion = changeData.new_version or nil}

View file

@ -7,7 +7,7 @@ local FactorioEntityData = ____kafkatorio_2Dschema.FactorioEntityData
local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData
local ____converters = require("main.events.converters") local ____converters = require("main.events.converters")
local Converters = ____converters.Converters local Converters = ____converters.Converters
local ____EventDataCache = require("main.cache.EventDataCache") local ____EventDataCache = require("main.emitting.EventDataCache")
local EventDataCache = ____EventDataCache.default local EventDataCache = ____EventDataCache.default
____exports.EntityUpdatesHandler = __TS__Class() ____exports.EntityUpdatesHandler = __TS__Class()
local EntityUpdatesHandler = ____exports.EntityUpdatesHandler local EntityUpdatesHandler = ____exports.EntityUpdatesHandler

View file

@ -10,8 +10,8 @@ local ____eventTypeCheck = require("main.events.eventTypeCheck")
local isEventType = ____eventTypeCheck.isEventType local isEventType = ____eventTypeCheck.isEventType
local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema") local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema")
local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData
local ____EventDataCache = require("main.cache.EventDataCache") local ____EventDataCache = require("main.emitting.EventDataCache")
local EventUpdatesManager = ____EventDataCache.default local EventUpdates = ____EventDataCache.default
local MapChunkUpdateHandler = __TS__Class() local MapChunkUpdateHandler = __TS__Class()
MapChunkUpdateHandler.name = "MapChunkUpdateHandler" MapChunkUpdateHandler.name = "MapChunkUpdateHandler"
function MapChunkUpdateHandler.prototype.____constructor(self) function MapChunkUpdateHandler.prototype.____constructor(self)
@ -124,7 +124,7 @@ function MapChunkUpdateHandler.prototype.handlePreChunkDeleted(self, event)
end end
for ____, position in ipairs(event.positions) do for ____, position in ipairs(event.positions) do
local key = {surfaceIndex = surface.index, chunkPosition = {position.x, position.y}} local key = {surfaceIndex = surface.index, chunkPosition = {position.x, position.y}}
EventUpdatesManager:debounce( EventUpdates:debounce(
key, key,
KafkatorioPacketData.Type.MapChunkTileUpdate, KafkatorioPacketData.Type.MapChunkTileUpdate,
function(data) function(data)
@ -141,7 +141,7 @@ function MapChunkUpdateHandler.mapTilesUpdateDebounce(self, surface, chunkPositi
end end
local eventName = Converters.eventNameString(event.name) local eventName = Converters.eventNameString(event.name)
local key = {surfaceIndex = surface.index, chunkPosition = chunkPosition} local key = {surfaceIndex = surface.index, chunkPosition = chunkPosition}
EventUpdatesManager:debounce( EventUpdates:debounce(
key, key,
KafkatorioPacketData.Type.MapChunkTileUpdate, KafkatorioPacketData.Type.MapChunkTileUpdate,
function(data) function(data)

View file

@ -6,8 +6,8 @@ local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema")
local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData
local ____converters = require("main.events.converters") local ____converters = require("main.events.converters")
local Converters = ____converters.Converters local Converters = ____converters.Converters
local ____EventDataCache = require("main.cache.EventDataCache") local ____EventDataCache = require("main.emitting.EventDataCache")
local EventUpdatesManager = ____EventDataCache.default local EventUpdates = ____EventDataCache.default
____exports.PlayerUpdateHandler = __TS__Class() ____exports.PlayerUpdateHandler = __TS__Class()
local PlayerUpdateHandler = ____exports.PlayerUpdateHandler local PlayerUpdateHandler = ____exports.PlayerUpdateHandler
PlayerUpdateHandler.name = "PlayerUpdateHandler" PlayerUpdateHandler.name = "PlayerUpdateHandler"
@ -31,7 +31,7 @@ function PlayerUpdateHandler.prototype.playerUpdateThrottle(self, event, mutate,
return return
end end
local eventName = Converters.eventNameString(event.name) local eventName = Converters.eventNameString(event.name)
EventUpdatesManager:throttle( EventUpdates:throttle(
{index = playerIndex}, {index = playerIndex},
KafkatorioPacketData.Type.PlayerUpdate, KafkatorioPacketData.Type.PlayerUpdate,
function(data) function(data)

View file

@ -4,7 +4,7 @@ local ____converters = require("main.events.converters")
local Converters = ____converters.Converters local Converters = ____converters.Converters
local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema") local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema")
local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData
local ____PacketEmitter = require("main.PacketEmitter") local ____PacketEmitter = require("main.emitting.PacketEmitter")
local packetEmitter = ____PacketEmitter.default local packetEmitter = ____PacketEmitter.default
local ____PlayerUpdateHandler = require("main.events.handlers.PlayerUpdateHandler") local ____PlayerUpdateHandler = require("main.events.handlers.PlayerUpdateHandler")
local PlayerUpdates = ____PlayerUpdateHandler.default local PlayerUpdates = ____PlayerUpdateHandler.default

View file

@ -1,25 +1,21 @@
local ____exports = {} local ____exports = {}
local emit, getMapTilePrototypes, getEntityPrototypes, convertItemGroup, convertItemSubgroup local emit, getMapTilePrototypes, getEntityPrototypes, convertItemGroup, convertItemSubgroup
local ____PacketEmitter = require("main.PacketEmitter")
local PacketEmitter = ____PacketEmitter.default
local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema") local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema")
local FactorioPrototype = ____kafkatorio_2Dschema.FactorioPrototype local FactorioPrototype = ____kafkatorio_2Dschema.FactorioPrototype
local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData
local ____converters = require("main.events.converters") local ____converters = require("main.events.converters")
local Converters = ____converters.Converters local Converters = ____converters.Converters
local ____KafkatorioPacketQueue = require("main.emitting.KafkatorioPacketQueue")
local KafkatorioPacketQueue = ____KafkatorioPacketQueue.default
function emit(protosByType) function emit(protosByType)
for ____, protos in pairs(protosByType) do for ____, protos in pairs(protosByType) do
PacketEmitter:emitInstantPacket({type = KafkatorioPacketData.Type.PrototypesUpdate, prototypes = protos}) KafkatorioPacketQueue:enqueue({type = KafkatorioPacketData.Type.PrototypesUpdate, prototypes = protos})
end end
end end
function getMapTilePrototypes() function getMapTilePrototypes()
local tiles = {} local tiles = {}
if not (tiles.tile ~= nil) then
tiles.tile = {}
end
for ____, tile in pairs(game.tile_prototypes) do for ____, tile in pairs(game.tile_prototypes) do
local ____tiles_tile_0 = tiles.tile tiles[#tiles + 1] = {
____tiles_tile_0[#____tiles_tile_0 + 1] = {
type = FactorioPrototype.Type.MapTile, type = FactorioPrototype.Type.MapTile,
protoId = Converters.prototypeId("tile", tile.name), protoId = Converters.prototypeId("tile", tile.name),
order = tile.order, order = tile.order,
@ -29,13 +25,13 @@ function getMapTilePrototypes()
canBeMined = tile.mineable_properties.minable canBeMined = tile.mineable_properties.minable
} }
end end
return tiles return {tiles = tiles}
end end
function getEntityPrototypes() function getEntityPrototypes()
local protos = {} local protos = {}
for ____, entity in pairs(game.entity_prototypes) do for ____, entity in pairs(game.entity_prototypes) do
local key = (entity.group.name .. "/") .. entity.subgroup.name local key = (entity.group.name .. "/") .. entity.subgroup.name
if not (protos[key] ~= nil) then if protos[key] == nil then
protos[key] = {} protos[key] = {}
end end
local entityProto = { local entityProto = {
@ -58,8 +54,8 @@ function getEntityPrototypes()
entityProto.mapColourEnemy = Converters.mapColour(entity.enemy_map_color) entityProto.mapColourEnemy = Converters.mapColour(entity.enemy_map_color)
end end
entityProto.miningProperties = Converters.miningProperties(entity.mineable_properties) entityProto.miningProperties = Converters.miningProperties(entity.mineable_properties)
local ____protos_key_1 = protos[key] local ____protos_key_0 = protos[key]
____protos_key_1[#____protos_key_1 + 1] = entityProto ____protos_key_0[#____protos_key_0 + 1] = entityProto
end end
return protos return protos
end end
@ -67,19 +63,17 @@ function convertItemGroup(itemGroup)
return {name = itemGroup.name, type = itemGroup.type, parentName = nil} return {name = itemGroup.name, type = itemGroup.type, parentName = nil}
end end
function convertItemSubgroup(itemGroup) function convertItemSubgroup(itemGroup)
local ____itemGroup_name_4 = itemGroup.name local ____itemGroup_name_3 = itemGroup.name
local ____itemGroup_type_5 = itemGroup.type local ____itemGroup_type_4 = itemGroup.type
local ____itemGroup_group_name_2 = itemGroup.group local ____itemGroup_group_name_1 = itemGroup.group
if ____itemGroup_group_name_2 ~= nil then if ____itemGroup_group_name_1 ~= nil then
____itemGroup_group_name_2 = ____itemGroup_group_name_2.name ____itemGroup_group_name_1 = ____itemGroup_group_name_1.name
end end
return {name = ____itemGroup_name_4, type = ____itemGroup_type_5, parentName = ____itemGroup_group_name_2} return {name = ____itemGroup_name_3, type = ____itemGroup_type_4, parentName = ____itemGroup_group_name_1}
end end
function ____exports.emitPrototypes() function ____exports.emitPrototypes()
local mapTileProtos = getMapTilePrototypes() local mapTileProtos = getMapTilePrototypes()
emit(mapTileProtos) emit(mapTileProtos)
local ____ = game.item_group_prototypes
local ____ = game.item_subgroup_prototypes
local entityProtos = getEntityPrototypes() local entityProtos = getEntityPrototypes()
emit(entityProtos) emit(entityProtos)
end end

View file

@ -1,16 +1,20 @@
local ____exports = {} local ____exports = {}
local ____queue = require("main.queue.queue")
local Queue = ____queue.Queue
local ____eventTypeCheck = require("main.events.eventTypeCheck") local ____eventTypeCheck = require("main.events.eventTypeCheck")
local isEventType = ____eventTypeCheck.isEventType local isEventType = ____eventTypeCheck.isEventType
local ____converters = require("main.events.converters") local ____converters = require("main.events.converters")
local Converters = ____converters.Converters local Converters = ____converters.Converters
local ____surfaceUpdates = require("main.events.surfaceUpdates") local ____surfaceUpdates = require("main.events.surfaceUpdates")
local handleSurfaceUpdate = ____surfaceUpdates.handleSurfaceUpdate local handleSurfaceUpdate = ____surfaceUpdates.handleSurfaceUpdate
local ____EventDataCache = require("main.cache.EventDataCache") local ____EventDataCache = require("main.emitting.EventDataCache")
local EventCacheService = ____EventDataCache.default local EventCacheService = ____EventDataCache.default
local ____MapChunkUpdateHandler = require("main.events.handlers.MapChunkUpdateHandler") local ____MapChunkUpdateHandler = require("main.events.handlers.MapChunkUpdateHandler")
local MapChunkUpdate = ____MapChunkUpdateHandler.default local MapChunkUpdate = ____MapChunkUpdateHandler.default
local ____queue = require("main.queue.queue")
local EventDataQueue = ____queue.default
local ____KafkatorioPacketQueue = require("main.emitting.KafkatorioPacketQueue")
local KafkatorioPacketQueue = ____KafkatorioPacketQueue.default
local ____PacketEmitter = require("main.emitting.PacketEmitter")
local PacketEmitter = ____PacketEmitter.default
script.on_event( script.on_event(
defines.events.on_tick, defines.events.on_tick,
function(event) function(event)
@ -23,6 +27,10 @@ script.on_event(
) )
end end
end end
local kPacket = KafkatorioPacketQueue:dequeue()
if kPacket ~= nil then
PacketEmitter:emitInstantPacket(kPacket)
end
if event.tick % 6 == 0 then if event.tick % 6 == 0 then
local emittedCount = EventCacheService:extractAndEmitExpiredPackets() local emittedCount = EventCacheService:extractAndEmitExpiredPackets()
if emittedCount > 0 then if emittedCount > 0 then
@ -30,9 +38,9 @@ script.on_event(
end end
end end
if event.tick % 7 == 0 then if event.tick % 7 == 0 then
local events = Queue.dequeueValues(1) local events = EventDataQueue:dequeueValues(1)
if #events > 0 then if #events > 0 then
log((((("[on_tick:" .. tostring(event.tick)) .. "] dequeued ") .. tostring(#events)) .. " events, current size: ") .. tostring(Queue.size())) log((((("[on_tick:" .. tostring(event.tick)) .. "] dequeued ") .. tostring(#events)) .. " events, current size: ") .. tostring(EventDataQueue:size()))
local i = 1 local i = 1
for ____, event in ipairs(events) do for ____, event in ipairs(events) do
if isEventType(event, defines.events.on_chunk_generated) then if isEventType(event, defines.events.on_chunk_generated) then

View file

@ -1,5 +1,5 @@
local ____exports = {} local ____exports = {}
local ____PacketEmitter = require("main.PacketEmitter") local ____PacketEmitter = require("main.emitting.PacketEmitter")
local PacketEmitter = ____PacketEmitter.default local PacketEmitter = ____PacketEmitter.default
local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema") local ____kafkatorio_2Dschema = require("generated.kafkatorio-schema")
local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData local KafkatorioPacketData = ____kafkatorio_2Dschema.KafkatorioPacketData

View file

@ -1,8 +1,8 @@
local ____exports = {} local ____exports = {}
local ____EventDataCache = require("main.emitting.EventDataCache")
local EventUpdates = ____EventDataCache.default
local ____queue = require("main.queue.queue") local ____queue = require("main.queue.queue")
local Queue = ____queue.Queue local EventDataQueueManager = ____queue.EventDataQueueManager
local ____EventDataCache = require("main.cache.EventDataCache")
local EventUpdatesManager = ____EventDataCache.default
function ____exports.initGlobal(force) function ____exports.initGlobal(force)
if force == nil then if force == nil then
force = false force = false
@ -10,8 +10,8 @@ function ____exports.initGlobal(force)
log(("Initialising Kafkatorio Global variables (force=" .. tostring(force)) .. ")...") log(("Initialising Kafkatorio Global variables (force=" .. tostring(force)) .. ")...")
global.MOD_VERSION = script.active_mods[script.mod_name] global.MOD_VERSION = script.active_mods[script.mod_name]
global.FACTORIO_VERSION = script.active_mods.base global.FACTORIO_VERSION = script.active_mods.base
Queue.init(true) EventDataQueueManager:init(true)
EventUpdatesManager:init(true) EventUpdates:init(true)
log("Finished initialising Kafkatorio Global variables") log("Finished initialising Kafkatorio Global variables")
end end
return ____exports return ____exports

View file

@ -1,84 +1,87 @@
local ____lualib = require("lualib_bundle") local ____lualib = require("lualib_bundle")
local __TS__New = ____lualib.__TS__New
local __TS__Class = ____lualib.__TS__Class local __TS__Class = ____lualib.__TS__Class
local __TS__New = ____lualib.__TS__New
local ____exports = {} local ____exports = {}
____exports.Queue = {} local Node
local Queue = ____exports.Queue ____exports.EventDataQueueManager = __TS__Class()
do local EventDataQueueManager = ____exports.EventDataQueueManager
local Node EventDataQueueManager.name = "EventDataQueueManager"
function Queue.init(force) function EventDataQueueManager.prototype.____constructor(self)
local isAnythingUndefined = global.store == nil or global.size == nil or global.head == nil or global.tail == nil end
log(((("Initialising EventQueue globals (force=" .. tostring(force)) .. ", isAnythingUndefined=") .. tostring(isAnythingUndefined)) .. ")") function EventDataQueueManager.init(self, force)
if force == true or isAnythingUndefined then local isAnythingUndefined = global.store == nil or global.size == nil or global.head == nil or global.tail == nil
global.store = {} log(((("Initialising EventDataQueueManager globals (force=" .. tostring(force)) .. ", isAnythingUndefined=") .. tostring(isAnythingUndefined)) .. ")")
global.head = nil if force == true or isAnythingUndefined then
global.tail = nil global.store = {}
global.size = 0 global.head = nil
end global.tail = nil
log("Finished initialising EventQueue") global.size = 0
end end
function Queue.size() log("Finished initialising EventDataQueueManager")
return global.size end
function EventDataQueueManager.prototype.size(self)
return global.size
end
function EventDataQueueManager.prototype.enqueue(self, key, event, weight)
if weight == nil then
weight = 1
end end
function Queue.enqueue(key, event, weight) if global.store[key] ~= nil then
if weight == nil then local node = __TS__New(Node, key, weight)
weight = 1
end
if global.store[key] ~= nil then
local node = __TS__New(Node, key, weight)
if global.head == nil then
global.head = node
end
if global.tail ~= nil then
global.tail.next = node
end
global.tail = node
global.size = global.size + 1
end
global.store[key] = event
end
function Queue.dequeue()
if global.head == nil then if global.head == nil then
global.head = node
end
if global.tail ~= nil then
global.tail.next = node
end
global.tail = node
global.size = global.size + 1
end
global.store[key] = event
end
function EventDataQueueManager.prototype.dequeue(self)
if global.head == nil then
return nil
else
local weight = global.head.weight
local storeKey = global.head.storeKey
global.head = global.head.next
global.size = global.size - 1
local storedPacket = global.store[storeKey]
global.store[storeKey] = nil
if storedPacket == nil then
return nil return nil
else else
local weight = global.head.weight return {value = storedPacket, weight = weight}
local storeKey = global.head.storeKey
global.head = global.head.next
global.size = global.size - 1
local storedPacket = global.store[storeKey]
global.store[storeKey] = nil
if storedPacket == nil then
return nil
else
return {value = storedPacket, weight = weight}
end
end end
end end
function Queue.dequeueValues(targetWeight) end
if targetWeight == nil then function EventDataQueueManager.prototype.dequeueValues(self, targetWeight)
targetWeight = 50 if targetWeight == nil then
end targetWeight = 50
local values = {} end
local weight = 0 local values = {}
repeat local weight = 0
do repeat
local packet = Queue.dequeue() do
if packet == nil then local packet = self:dequeue()
break if packet == nil then
else break
weight = weight + packet.weight else
values[#values + 1] = packet.value weight = weight + packet.weight
end values[#values + 1] = packet.value
end end
until not (weight <= targetWeight) end
return values until not (weight <= targetWeight)
end return values
Node = __TS__Class() end
Node.name = "Node" local EventDataQueue = __TS__New(____exports.EventDataQueueManager)
function Node.prototype.____constructor(self, storeKey, weight) ____exports.default = EventDataQueue
self.next = nil Node = __TS__Class()
self.storeKey = storeKey Node.name = "Node"
self.weight = weight function Node.prototype.____constructor(self, storeKey, weight)
end self.next = nil
self.storeKey = storeKey
self.weight = weight
end end
return ____exports return ____exports

View file

@ -1,235 +0,0 @@
import KafkatorioSettings from "../settings/KafkatorioSettings";
import {KafkatorioKeyedPacketData} from "../types";
import packetEmitter from "../PacketEmitter";
export namespace EventUpdates {
declare const global: {
/** Map the hash of the key, as a string, a cache entry */
cache: LuaTable<string, CacheEntry<KafkatorioKeyedPacketData>>,
}
export class Manager {
init(force?: boolean) {
const isAnythingUndefined = global.cache == undefined
log(`Initialising EventDataCache (force=${force}, isAnythingUndefined=${isAnythingUndefined})...`)
if (force == true || isAnythingUndefined) {
log("Initialising global.cache")
global.cache = new LuaTable<string, CacheEntry<KafkatorioKeyedPacketData>>()
}
log(`Finished initialising EventDataCache`)
}
/**
* Update the cache data without restarting the 'time-to-emit' countdown, so that the data won't
* be emitted more than once per {@link CacheEntry.expirationDurationTicks}.
*/
public throttle<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
mutate: CacheDataMutator<PACKET>,
expirationDurationTicks?: uint,
) {
this.update(key, type, mutate, false, expirationDurationTicks)
}
/**
* Update the cache data and reset the 'time-to-emit' countdown, so the data won't be emitted
* until there's an inactivity gap of {@link CacheEntry.expirationDurationTicks}.
*/
public debounce<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
mutate: CacheDataMutator<PACKET>,
expirationDurationTicks?: uint,
) {
this.update(key, type, mutate, true, expirationDurationTicks)
}
private update<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
mutate: CacheDataMutator<PACKET>,
resetLastUpdated: boolean,
expirationDurationTicks?: uint,
) {
const entry: CacheEntry<PACKET> = this.getCacheEntry(key, type) ??
Manager.createCacheEntry(key, type)
mutate(entry.packet)
if (resetLastUpdated) {
entry.lastUpdatedTick = game.tick
}
if (expirationDurationTicks != undefined) {
entry.expirationDurationTicks = expirationDurationTicks
}
if (entry.expirationDurationTicks != undefined && entry.expirationDurationTicks <= 0) {
this.extractAndEmitExpiredPackets()
}
}
/**
* Usage example: If an urgent update comes in, then the expiration can be reduced so that
* it's transmitted sooner.
*/
public setExpiration<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
expirationDurationTicks: uint,
) {
const entry = this.getCacheEntry(key, type)
if (entry != undefined) {
entry.expirationDurationTicks = expirationDurationTicks
}
}
/** Removes all expired entries from the cache, and return them. */
// use in/out v4.7.0 https://github.com/microsoft/TypeScript/pull/48240
private extractExpired(): Array<KafkatorioKeyedPacketData> {
let countExpired = 0
let countTotal = 0
const expiredData: Array<KafkatorioKeyedPacketData> = []
for (const [key, entry] of global.cache) {
countTotal++
if (this.isExpired(entry)) {
countExpired++
expiredData.push(entry.packet)
global.cache.delete(key)
}
}
const hasExpiredItems = countExpired > 0
const hasAnyItem = countTotal > 0
if (hasExpiredItems || (game.tick % 30 == 0 && hasAnyItem)) {
log(`[extractExpired] expired items: ${countExpired}, total: ${countTotal}`)
}
return expiredData
}
/**
* Removes and emits all expired packets.
*
* @return the number of emitted packets
*/
public extractAndEmitExpiredPackets() {
const cachedEvents: Array<KafkatorioKeyedPacketData> = this.extractExpired()
if (cachedEvents.length > 0) {
log(`[EventDataCache] emitting ${cachedEvents.length} events on tick ${game.tick}`)
for (const event of cachedEvents) {
packetEmitter.emitKeyedPacket(event)
}
}
return cachedEvents.length
}
private getCacheEntry<PACKET extends KafkatorioKeyedPacketData>(
key: EventUpdates.PacketKey<PACKET>,
type: EventUpdates.PacketType<PACKET>,
): CacheEntry<PACKET> | undefined {
const hash = Manager.hashKey(key)
if (!global.cache.has(hash)) {
return undefined
}
const value: CacheEntry<any> = global.cache.get(hash)
if (this.isEntryInstanceOf(value, type)) {
return value
} else {
// Type mismatch. This shouldn't happen...
return undefined
}
}
private static createCacheEntry<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
): CacheEntry<PACKET> {
const data: PACKET = <PACKET>{
type: type,
key: key,
}
const entry: CacheEntry<PACKET> = new CacheEntry<PACKET>(data)
const hash = Manager.hashKey(key)
global.cache.set(hash, entry)
return entry
}
isExpired<TYPE extends KafkatorioKeyedPacketData>(entry: CacheEntry<TYPE>): boolean {
const expiryDuration =
entry.expirationDurationTicks
?? KafkatorioSettings.getEventCacheExpirationTicks(entry.packet)
if (expiryDuration == undefined) {
return true
} else {
return game.tick - entry.lastUpdatedTick > expiryDuration
}
}
isEntryInstanceOf<PACKET extends KafkatorioKeyedPacketData>(
entry: CacheEntry<any> | undefined,
type: EventUpdates.PacketType<PACKET>,
): entry is CacheEntry<PACKET> {
return this.isDataInstanceOf(entry?.packet, type)
}
isDataInstanceOf<PACKET extends KafkatorioKeyedPacketData>(
packet: PACKET | undefined,
type: EventUpdates.PacketType<PACKET>,
): packet is PACKET {
return packet != undefined && packet.type == type
}
private static hashKey<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
): string {
return game.encode_string(game.table_to_json(key))!!
}
}
export type PacketKey<PACKET extends KafkatorioKeyedPacketData> = PACKET["key"]
export type PacketType<PACKET extends KafkatorioKeyedPacketData> = PACKET["type"]
export type CacheDataMutator<PACKET extends KafkatorioKeyedPacketData> = (packet: PACKET) => void
class CacheEntry<PACKET extends KafkatorioKeyedPacketData> {
lastUpdatedTick: uint = game.tick
expirationDurationTicks?: uint
packet: PACKET
constructor(
packet: PACKET,
expirationDurationTicks?: uint,
) {
this.packet = packet
this.expirationDurationTicks = expirationDurationTicks
}
}
}
const EventUpdatesManager = new EventUpdates.Manager()
export default EventUpdatesManager

View file

@ -1,6 +1,6 @@
import {initGlobal} from "../global-init"; import {initGlobal} from "../global-init";
import {Queue} from "../queue/queue";
import {emitPrototypes} from "../events/prototypeUpdates"; import {emitPrototypes} from "../events/prototypeUpdates";
import EventDataQueue from "../queue/queue";
commands.add_command( commands.add_command(
@ -58,7 +58,7 @@ commands.add_command(
tick: e.tick, tick: e.tick,
} }
Queue.enqueue( EventDataQueue.enqueue(
`${surface.index}${chunk.x}${chunk.y}`, `${surface.index}${chunk.x}${chunk.y}`,
data, data,
50 50

View file

@ -0,0 +1,240 @@
import KafkatorioSettings from "../settings/KafkatorioSettings";
import {KafkatorioKeyedPacketData} from "../types";
import PacketEmitter from "./PacketEmitter";
declare const global: {
/** Map the hash of the key, as a string, a cache entry */
eventUpdatesManagerCache: LuaTable<string, CacheEntry<KafkatorioKeyedPacketData>>,
}
export class EventUpdatesManager {
constructor() {
this.init()
}
init(force?: boolean) {
const isAnythingUndefined = global.eventUpdatesManagerCache == undefined
log(`Initialising EventDataCache (force=${force}, isAnythingUndefined=${isAnythingUndefined})...`)
if (force == true || isAnythingUndefined) {
log("Initialising global.cache")
global.eventUpdatesManagerCache = new LuaTable<string, CacheEntry<KafkatorioKeyedPacketData>>()
}
log(`Finished initialising EventDataCache`)
}
/**
* Update the cache data without restarting the 'time-to-emit' countdown, so that the data won't
* be emitted more than once per {@link CacheEntry.expirationDurationTicks}.
*/
public throttle<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
mutate: CacheDataMutator<PACKET>,
expirationDurationTicks?: uint,
) {
this.update(key, type, mutate, false, expirationDurationTicks)
}
/**
* Update the cache data and reset the 'time-to-emit' countdown, so the data won't be emitted
* until there's an inactivity gap of {@link CacheEntry.expirationDurationTicks}.
*/
public debounce<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
mutate: CacheDataMutator<PACKET>,
expirationDurationTicks?: uint,
) {
this.update(key, type, mutate, true, expirationDurationTicks)
}
private update<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
mutate: CacheDataMutator<PACKET>,
resetLastUpdated: boolean,
expirationDurationTicks?: uint,
) {
const entry: CacheEntry<PACKET> = this.getCacheEntry(key, type) ??
EventUpdatesManager.createCacheEntry(key, type)
mutate(entry.packet)
if (resetLastUpdated) {
entry.lastUpdatedTick = game.tick
}
if (expirationDurationTicks != undefined) {
entry.expirationDurationTicks = expirationDurationTicks
}
if (entry.expirationDurationTicks != undefined && entry.expirationDurationTicks <= 0) {
this.extractAndEmitExpiredPackets()
}
}
/**
* Usage example: If an urgent update comes in, then the expiration can be reduced so that
* it's transmitted sooner.
*/
public setExpiration<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
expirationDurationTicks: uint,
) {
const entry = this.getCacheEntry(key, type)
if (entry != undefined) {
entry.expirationDurationTicks = expirationDurationTicks
}
}
/** Removes all expired entries from the cache, and return them. */
// use in/out v4.7.0 https://github.com/microsoft/TypeScript/pull/48240
private extractExpired(): Array<KafkatorioKeyedPacketData> {
let countExpired = 0
let countTotal = 0
const expiredData: Array<KafkatorioKeyedPacketData> = []
for (const [key, entry] of global.eventUpdatesManagerCache) {
countTotal++
if (this.isExpired(entry)) {
countExpired++
expiredData.push(entry.packet)
global.eventUpdatesManagerCache.delete(key)
}
}
const hasExpiredItems = countExpired > 0
const hasAnyItem = countTotal > 0
if (hasExpiredItems || (game.tick % 30 == 0 && hasAnyItem)) {
log(`[extractExpired] expired items: ${countExpired}, total: ${countTotal}`)
}
return expiredData
}
/**
* Removes and emits all expired packets.
*
* @return the number of emitted packets
*/
public extractAndEmitExpiredPackets() {
const cachedEvents: Array<KafkatorioKeyedPacketData> = this.extractExpired()
if (cachedEvents.length > 0) {
log(`[EventDataCache] emitting ${cachedEvents.length} events on tick ${game.tick}`)
for (const event of cachedEvents) {
PacketEmitter.emitKeyedPacket(event)
}
}
return cachedEvents.length
}
private getCacheEntry<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
): CacheEntry<PACKET> | undefined {
const hash = EventUpdatesManager.hashKey(key)
if (!global.eventUpdatesManagerCache.has(hash)) {
return undefined
}
const value: CacheEntry<any> = global.eventUpdatesManagerCache.get(hash)
if (this.isEntryInstanceOf(value, type)) {
return value
} else {
// Type mismatch. This shouldn't happen...
return undefined
}
}
private static createCacheEntry<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
type: PacketType<PACKET>,
): CacheEntry<PACKET> {
const data: PACKET = <PACKET>{
type: type,
key: key,
}
const entry: CacheEntry<PACKET> = new CacheEntry<PACKET>(data)
const hash = EventUpdatesManager.hashKey(key)
global.eventUpdatesManagerCache.set(hash, entry)
return entry
}
isExpired<TYPE extends KafkatorioKeyedPacketData>(entry: CacheEntry<TYPE>): boolean {
const expiryDuration =
entry.expirationDurationTicks
?? KafkatorioSettings.getEventCacheExpirationTicks(entry.packet)
if (expiryDuration == undefined) {
return true
} else {
return game.tick - entry.lastUpdatedTick > expiryDuration
}
}
isEntryInstanceOf<PACKET extends KafkatorioKeyedPacketData>(
entry: CacheEntry<any> | undefined,
type: PacketType<PACKET>,
): entry is CacheEntry<PACKET> {
return this.isDataInstanceOf(entry?.packet, type)
}
isDataInstanceOf<PACKET extends KafkatorioKeyedPacketData>(
packet: PACKET | undefined,
type: PacketType<PACKET>,
): packet is PACKET {
return packet != undefined && packet.type == type
}
private static hashKey<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
): string {
return game.encode_string(game.table_to_json(key))!!
}
}
const EventUpdates = new EventUpdatesManager()
export default EventUpdates
export type PacketKey<PACKET extends KafkatorioKeyedPacketData> = PACKET["key"]
export type PacketType<PACKET extends KafkatorioKeyedPacketData> = PACKET["type"]
export type CacheDataMutator<PACKET extends KafkatorioKeyedPacketData> = (packet: PACKET) => void
class CacheEntry<PACKET extends KafkatorioKeyedPacketData> {
lastUpdatedTick: uint = game.tick
expirationDurationTicks?: uint
packet: PACKET
constructor(
packet: PACKET,
expirationDurationTicks?: uint,
) {
this.packet = packet
this.expirationDurationTicks = expirationDurationTicks
}
}

View file

@ -0,0 +1,48 @@
import {KafkatorioPacketData} from "../../generated/kafkatorio-schema";
export class KafkatorioPacketQueueManager {
private size: int = 0
private first: Node<any> | null = null
private last: Node<any> | null = null
public get length() {
return this.size
}
public enqueue<T extends KafkatorioPacketData>(packet: T) {
const last = this.last
this.last = {
value: packet
}
if (last != null) {
last.next = this.last
}
if (this.first == null) {
this.first = this.last
}
this.size++
}
public dequeue<T extends KafkatorioPacketData>(): T | null {
const first = this.first
if (first == null) {
return null
}
this.first = first.next ?? null
this.size--
return first.value
}
}
const KafkatorioPacketQueue = new KafkatorioPacketQueueManager()
export default KafkatorioPacketQueue
interface Node<T extends KafkatorioPacketData> {
value: T
next?: Node<T>
}

View file

@ -1,8 +1,5 @@
import { import {KafkatorioPacket, KafkatorioPacketData} from "../../generated/kafkatorio-schema";
KafkatorioPacket, import {KafkatorioKeyedPacketData} from "../types";
KafkatorioPacketData
} from "../generated/kafkatorio-schema";
import {KafkatorioKeyedPacketData} from "./types";
export class KafkatorioPacketEmitter { export class KafkatorioPacketEmitter {

View file

@ -1,33 +1,26 @@
import PacketEmitter from "../PacketEmitter";
import {KafkatorioPacketData} from "../../generated/kafkatorio-schema"; import {KafkatorioPacketData} from "../../generated/kafkatorio-schema";
import Type = KafkatorioPacketData.Type; import KafkatorioPacketQueue from "../emitting/KafkatorioPacketQueue";
script.on_event( script.on_event(defines.events.on_console_chat, (event: OnConsoleChatEvent) => {
defines.events.on_console_chat, const update: KafkatorioPacketData.ConsoleChatUpdate = {
(event: OnConsoleChatEvent) => { type: KafkatorioPacketData.Type.ConsoleChatUpdate,
const update: KafkatorioPacketData.ConsoleChatUpdate = {
type: Type.ConsoleChatUpdate,
authorPlayerIndex: event.player_index ?? null, authorPlayerIndex: event.player_index ?? null,
content: event.message content: event.message
} }
PacketEmitter.emitInstantPacket(update) KafkatorioPacketQueue.enqueue(update)
} })
)
script.on_event( script.on_event(defines.events.on_console_command, (event: OnConsoleCommandEvent) => {
defines.events.on_console_command, const update: KafkatorioPacketData.ConsoleCommandUpdate = {
(event: OnConsoleCommandEvent) => { type: KafkatorioPacketData.Type.ConsoleCommandUpdate,
const update: KafkatorioPacketData.ConsoleCommandUpdate = {
type: Type.ConsoleCommandUpdate,
authorPlayerIndex: event.player_index ?? null, authorPlayerIndex: event.player_index ?? null,
command: event.command, command: event.command,
parameters: event.parameters, parameters: event.parameters,
} }
PacketEmitter.emitInstantPacket(update) KafkatorioPacketQueue.enqueue(update)
} })
)

View file

@ -3,8 +3,9 @@ import {
ConfigurationUpdateModData, ConfigurationUpdateModData,
KafkatorioPacketData KafkatorioPacketData
} from "../../../generated/kafkatorio-schema"; } from "../../../generated/kafkatorio-schema";
import PacketEmitter from "../../PacketEmitter"; import PacketEmitter from "../../emitting/PacketEmitter";
import ConfigurationUpdate = KafkatorioPacketData.ConfigurationUpdate; import ConfigurationUpdate = KafkatorioPacketData.ConfigurationUpdate;
import KafkatorioPacketQueue from "../../emitting/KafkatorioPacketQueue";
export class ConfigurationUpdateHandler { export class ConfigurationUpdateHandler {
@ -20,7 +21,7 @@ export class ConfigurationUpdateHandler {
factorioData: ConfigurationUpdateHandler.factorioData(changeData) factorioData: ConfigurationUpdateHandler.factorioData(changeData)
} }
PacketEmitter.emitInstantPacket(configUpdateData) KafkatorioPacketQueue.enqueue(configUpdateData)
} }

View file

@ -6,7 +6,7 @@ import {
Tick Tick
} from "../../../generated/kafkatorio-schema"; } from "../../../generated/kafkatorio-schema";
import {Converters} from "../converters"; import {Converters} from "../converters";
import EventDataCache from "../../cache/EventDataCache"; import EventDataCache from "../../emitting/EventDataCache";
export class EntityUpdatesHandler { export class EntityUpdatesHandler {

View file

@ -3,13 +3,11 @@ import {isEventType} from "../eventTypeCheck";
import { import {
KafkatorioPacketData, KafkatorioPacketData,
MapChunkPosition, MapChunkPosition,
MinedProduct,
PrototypeKey PrototypeKey
} from "../../../generated/kafkatorio-schema"; } from "../../../generated/kafkatorio-schema";
import EventUpdatesManager, {EventUpdates} from "../../cache/EventDataCache"; import EventUpdates, {PacketKey} from "../../emitting/EventDataCache";
import {MapChunkUpdateEvent, MapTileChangeEvent} from "../mapChunkUpdates"; import {MapChunkUpdateEvent, MapTileChangeEvent} from "../mapChunkUpdates";
import PacketKey = EventUpdates.PacketKey;
import Type = MinedProduct.Type;
type MapChunkTileUpdater = (data: KafkatorioPacketData.MapChunkTileUpdate) => void type MapChunkTileUpdater = (data: KafkatorioPacketData.MapChunkTileUpdate) => void
@ -141,7 +139,7 @@ class MapChunkUpdateHandler {
chunkPosition: [position.x, position.y], chunkPosition: [position.x, position.y],
} }
EventUpdatesManager.debounce<KafkatorioPacketData.MapChunkTileUpdate>( EventUpdates.debounce<KafkatorioPacketData.MapChunkTileUpdate>(
key, key,
KafkatorioPacketData.Type.MapChunkTileUpdate, KafkatorioPacketData.Type.MapChunkTileUpdate,
data => data.isDeleted = true, data => data.isDeleted = true,
@ -171,7 +169,7 @@ class MapChunkUpdateHandler {
chunkPosition: chunkPosition, chunkPosition: chunkPosition,
} }
EventUpdatesManager.debounce<KafkatorioPacketData.MapChunkTileUpdate>( EventUpdates.debounce<KafkatorioPacketData.MapChunkTileUpdate>(
key, key,
KafkatorioPacketData.Type.MapChunkTileUpdate, KafkatorioPacketData.Type.MapChunkTileUpdate,
data => { data => {

View file

@ -1,6 +1,6 @@
import {KafkatorioPacketData} from "../../../generated/kafkatorio-schema"; import {KafkatorioPacketData} from "../../../generated/kafkatorio-schema";
import {Converters} from "../converters"; import {Converters} from "../converters";
import EventUpdatesManager from "../../cache/EventDataCache"; import EventUpdates from "../../emitting/EventDataCache";
export type PlayerUpdater = (player: LuaPlayer, data: KafkatorioPacketData.PlayerUpdate) => void export type PlayerUpdater = (player: LuaPlayer, data: KafkatorioPacketData.PlayerUpdate) => void
@ -43,7 +43,7 @@ export class PlayerUpdateHandler {
const eventName = Converters.eventNameString(event.name) const eventName = Converters.eventNameString(event.name)
EventUpdatesManager.throttle<KafkatorioPacketData.PlayerUpdate>( EventUpdates.throttle<KafkatorioPacketData.PlayerUpdate>(
{index: playerIndex}, {index: playerIndex},
KafkatorioPacketData.Type.PlayerUpdate, KafkatorioPacketData.Type.PlayerUpdate,
data => { data => {

View file

@ -1,6 +1,6 @@
import {Converters} from "./converters"; import {Converters} from "./converters";
import {ForceIndex, KafkatorioPacketData, PlayerUpdateKey} from "../../generated/kafkatorio-schema"; import {ForceIndex, KafkatorioPacketData, PlayerUpdateKey} from "../../generated/kafkatorio-schema";
import packetEmitter from "../PacketEmitter"; import packetEmitter from "../emitting/PacketEmitter";
import PlayerUpdates from "./handlers/PlayerUpdateHandler"; import PlayerUpdates from "./handlers/PlayerUpdateHandler";

View file

@ -1,13 +1,14 @@
import PacketEmitter from "../PacketEmitter"; import PacketEmitter from "../emitting/PacketEmitter";
import { import {
EntityItemGroup, EntityItemGroup,
FactorioPrototype, FactorioPrototype,
KafkatorioPacketData KafkatorioPacketData
} from "../../generated/kafkatorio-schema"; } from "../../generated/kafkatorio-schema";
import {Converters} from "./converters"; import {Converters} from "./converters";
import KafkatorioPacketQueue from "../emitting/KafkatorioPacketQueue";
type PrototypesByType<T extends FactorioPrototype> = LuaTable<string, T[]> type PrototypesByType<T extends FactorioPrototype> = Record<string, T[]>
export function emitPrototypes() { export function emitPrototypes() {
@ -16,10 +17,6 @@ export function emitPrototypes() {
emit(mapTileProtos) emit(mapTileProtos)
game.item_group_prototypes
game.item_subgroup_prototypes
const entityProtos: PrototypesByType<FactorioPrototype.Entity> = getEntityPrototypes() const entityProtos: PrototypesByType<FactorioPrototype.Entity> = getEntityPrototypes()
emit(entityProtos) emit(entityProtos)
} }
@ -27,7 +24,7 @@ export function emitPrototypes() {
function emit<T extends FactorioPrototype>(protosByType: PrototypesByType<T>) { function emit<T extends FactorioPrototype>(protosByType: PrototypesByType<T>) {
for (const [, protos] of pairs(protosByType)) { for (const [, protos] of pairs(protosByType)) {
PacketEmitter.emitInstantPacket({ KafkatorioPacketQueue.enqueue({
type: KafkatorioPacketData.Type.PrototypesUpdate, type: KafkatorioPacketData.Type.PrototypesUpdate,
prototypes: protos, prototypes: protos,
}) })
@ -36,13 +33,10 @@ function emit<T extends FactorioPrototype>(protosByType: PrototypesByType<T>) {
function getMapTilePrototypes(): PrototypesByType<FactorioPrototype.MapTile> { function getMapTilePrototypes(): PrototypesByType<FactorioPrototype.MapTile> {
const tiles = new LuaTable<string, FactorioPrototype.MapTile[]>() const tiles: FactorioPrototype.MapTile[] = []
if (!tiles.has("tile")) {
tiles.set("tile", [])
}
for (const [, tile] of game.tile_prototypes) { for (const [, tile] of game.tile_prototypes) {
tiles.get("tile").push({ tiles[tiles.length] = {
type: FactorioPrototype.Type.MapTile, type: FactorioPrototype.Type.MapTile,
protoId: Converters.prototypeId("tile", tile.name), protoId: Converters.prototypeId("tile", tile.name),
@ -51,21 +45,22 @@ function getMapTilePrototypes(): PrototypesByType<FactorioPrototype.MapTile> {
collisionMasks: Converters.convertCollisionMaskToNames(tile.collision_mask), collisionMasks: Converters.convertCollisionMaskToNames(tile.collision_mask),
mapColour: Converters.mapColour(tile.map_color), mapColour: Converters.mapColour(tile.map_color),
canBeMined: tile.mineable_properties.minable, canBeMined: tile.mineable_properties.minable,
}) }
}
return {
"tiles": tiles
} }
return tiles
} }
function getEntityPrototypes(): PrototypesByType<FactorioPrototype.Entity> { function getEntityPrototypes(): PrototypesByType<FactorioPrototype.Entity> {
const protos = new LuaTable<string, FactorioPrototype.Entity[]>() const protos: PrototypesByType<FactorioPrototype.Entity> = {}
for (const [, entity] of game.entity_prototypes) { for (const [, entity] of game.entity_prototypes) {
const key: string = `${entity.group.name}/${entity.subgroup.name}` const key: string = `${entity.group.name}/${entity.subgroup.name}`
if (!protos.has(key)) { protos[key] ??= []
protos.set(key, [])
}
const entityProto: FactorioPrototype.Entity = { const entityProto: FactorioPrototype.Entity = {
type: FactorioPrototype.Type.Entity, type: FactorioPrototype.Type.Entity,
@ -91,7 +86,7 @@ function getEntityPrototypes(): PrototypesByType<FactorioPrototype.Entity> {
} }
entityProto.miningProperties = Converters.miningProperties(entity.mineable_properties) entityProto.miningProperties = Converters.miningProperties(entity.mineable_properties)
protos.get(key).push(entityProto) protos[key].push(entityProto)
} }
return protos return protos
} }

View file

@ -1,45 +1,50 @@
import {Queue} from "../queue/queue";
import {isEventType} from "./eventTypeCheck"; import {isEventType} from "./eventTypeCheck";
import {Converters} from "./converters"; import {Converters} from "./converters";
import {handleSurfaceUpdate} from "./surfaceUpdates"; import {handleSurfaceUpdate} from "./surfaceUpdates";
import EventCacheService from "../cache/EventDataCache"; import EventCacheService from "../emitting/EventDataCache";
import MapChunkUpdate from "./handlers/MapChunkUpdateHandler"; import MapChunkUpdate from "./handlers/MapChunkUpdateHandler";
import EventDataQueue from "../queue/queue";
import KafkatorioPacketQueue from "../emitting/KafkatorioPacketQueue";
import PacketEmitter from "../emitting/PacketEmitter";
import {KafkatorioPacketData} from "../../generated/kafkatorio-schema";
script.on_event( script.on_event(defines.events.on_tick, (event: OnTickEvent) => {
defines.events.on_tick, if (event.tick % 1000 == 0) {
(event: OnTickEvent) => { for (const [, surface] of game.surfaces) {
if (event.tick % 1000 == 0) { handleSurfaceUpdate(event, Converters.eventNameString(event.name), surface)
for (const [, surface] of game.surfaces) { }
handleSurfaceUpdate(event, Converters.eventNameString(event.name), surface) }
}
}
if (event.tick % 6 == 0) { const kPacket: KafkatorioPacketData | null = KafkatorioPacketQueue.dequeue()
// Every 0.1 seconds if (kPacket != null) {
const emittedCount = EventCacheService.extractAndEmitExpiredPackets() PacketEmitter.emitInstantPacket(kPacket)
if (emittedCount > 0) { }
log(`[on_tick:${event.tick}] emitted ${emittedCount} events`)
}
}
if (event.tick % 7 == 0) { if (event.tick % 6 == 0) {
const events: EventData[] = Queue.dequeueValues(1) // Every 0.1 seconds
const emittedCount = EventCacheService.extractAndEmitExpiredPackets()
if (emittedCount > 0) {
log(`[on_tick:${event.tick}] emitted ${emittedCount} events`)
}
}
if (events.length > 0) { if (event.tick % 7 == 0) {
log(`[on_tick:${event.tick}] dequeued ${events.length} events, current size: ${Queue.size()}`) const events: EventData[] = EventDataQueue.dequeueValues(1)
let i = 1 if (events.length > 0) {
for (const event of events) { log(`[on_tick:${event.tick}] dequeued ${events.length} events, current size: ${EventDataQueue.size()}`)
if (isEventType(event, defines.events.on_chunk_generated)) {
let eName = Converters.eventNameString(event.name)
log(`[on_tick:${event.tick}] dequed event ${eName}, delay ${i}`) let i = 1
MapChunkUpdate.handleChunkGeneratedEvent(event, i) for (const event of events) {
i += 1 if (isEventType(event, defines.events.on_chunk_generated)) {
} let eName = Converters.eventNameString(event.name)
}
log(`[on_tick:${event.tick}] dequed event ${eName}, delay ${i}`)
MapChunkUpdate.handleChunkGeneratedEvent(event, i)
i += 1
} }
} }
} }
) }
})

View file

@ -1,7 +1,4 @@
script.on_event( script.on_event(defines.events.on_runtime_mod_setting_changed, (e: OnRuntimeModSettingChangedEvent) => {
defines.events.on_runtime_mod_setting_changed, print(`Settings changed ${e.setting_type}, ${e.player_index}, ${e.setting}, ${e.mod_name}`)
(e: OnRuntimeModSettingChangedEvent) => { // KafkatorioSettings.loadSettings()
print(`Settings changed ${e.setting_type}, ${e.player_index}, ${e.setting}, ${e.mod_name}`) })
// KafkatorioSettings.loadSettings()
}
)

View file

@ -1,4 +1,4 @@
import PacketEmitter from "../PacketEmitter"; import PacketEmitter from "../emitting/PacketEmitter";
import {KafkatorioPacketData} from "../../generated/kafkatorio-schema"; import {KafkatorioPacketData} from "../../generated/kafkatorio-schema";
import Type = KafkatorioPacketData.Type; import Type = KafkatorioPacketData.Type;

View file

@ -1,5 +1,5 @@
import {Queue} from "./queue/queue"; import EventUpdates from "./emitting/EventDataCache";
import EventUpdatesManager from "./cache/EventDataCache"; import {EventDataQueueManager} from "./queue/queue";
export function initGlobal(force: boolean = false) { export function initGlobal(force: boolean = false) {
@ -8,8 +8,8 @@ export function initGlobal(force: boolean = false) {
global.MOD_VERSION = script.active_mods[script.mod_name] global.MOD_VERSION = script.active_mods[script.mod_name]
global.FACTORIO_VERSION = script.active_mods["base"] global.FACTORIO_VERSION = script.active_mods["base"]
Queue.init(true) EventDataQueueManager.init(true)
EventUpdatesManager.init(true) EventUpdates.init(true)
log("Finished initialising Kafkatorio Global variables") log("Finished initialising Kafkatorio Global variables")
} }

View file

@ -1,23 +1,13 @@
export namespace Queue { export class EventDataQueueManager {
declare const global: { static init(force?: boolean) {
store: EventQueueStore
head: Node | null
tail: Node | null
size: uint
}
export function init(force?: boolean) {
const isAnythingUndefined = global.store == undefined || const isAnythingUndefined = global.store == undefined ||
global.size == undefined || global.size == undefined ||
global.head == undefined || global.head == undefined ||
global.tail == undefined global.tail == undefined
log(`Initialising EventQueue globals (force=${force}, isAnythingUndefined=${isAnythingUndefined})`) log(`Initialising EventDataQueueManager globals (force=${force}, isAnythingUndefined=${isAnythingUndefined})`)
if (force == true || isAnythingUndefined) { if (force == true || isAnythingUndefined) {
global.store = {} global.store = {}
@ -26,16 +16,16 @@ export namespace Queue {
global.size = 0 global.size = 0
} }
log(`Finished initialising EventQueue`) log(`Finished initialising EventDataQueueManager`)
} }
export function size(): uint | undefined { size(): uint | undefined {
return global.size return global.size
} }
export function enqueue(key: string, event: EventData, weight: int = 1) { enqueue(key: string, event: EventData, weight: int = 1) {
if (key ! in global.store) { if (key ! in global.store) {
let node = new Node(key, weight) let node = new Node(key, weight)
if (global.head == null) { if (global.head == null) {
@ -52,7 +42,7 @@ export namespace Queue {
} }
export function dequeue(): WeightedPacket | null { dequeue(): WeightedPacket | null {
if (global.head == null) { if (global.head == null) {
return null return null
} else { } else {
@ -76,12 +66,12 @@ export namespace Queue {
} }
export function dequeueValues(targetWeight: uint = 50): EventData[] { dequeueValues(targetWeight: uint = 50): EventData[] {
let values: EventData[] = [] let values: EventData[] = []
let weight: uint = 0 let weight: uint = 0
do { do {
let packet = dequeue() let packet = this.dequeue()
if (packet == null) { if (packet == null) {
break; break;
@ -94,27 +84,43 @@ export namespace Queue {
return values return values
} }
}
class Node {
storeKey: string;
next: Node | null = null; const EventDataQueue = new EventDataQueueManager()
weight: uint
constructor(storeKey: string, weight: uint) { export default EventDataQueue
this.storeKey = storeKey;
this.weight = weight
} declare const global: {
} store: EventQueueStore
head: Node | null
interface EventQueueStore { tail: Node | null
[key: string]: EventData | null;
} size: uint
}
interface WeightedPacket {
value: EventData, class Node {
weight: int, storeKey: string;
} next: Node | null = null;
weight: uint
constructor(storeKey: string, weight: uint) {
this.storeKey = storeKey;
this.weight = weight
}
}
interface EventQueueStore {
[key: string]: EventData | null;
}
interface WeightedPacket {
value: EventData,
weight: int,
} }