finish first implementation of incremental updates
This commit is contained in:
parent
dd45bece53
commit
d14d0cd991
12 changed files with 614 additions and 97 deletions
|
|
@ -1,62 +1,78 @@
|
||||||
import {ExcludeNullKeys} from "../global";
|
import {ExcludeNullKeys, NonNullableKeys} from "../types";
|
||||||
|
|
||||||
export namespace EventDataCache {
|
export namespace EventDataCache {
|
||||||
|
|
||||||
|
|
||||||
declare const global: {
|
declare const global: {
|
||||||
cache: LuaTable<CacheKey<FactorioEventUpdate>, CacheEntry<FactorioEventUpdate>>,
|
cache: LuaTable<CacheKey<FactorioEventUpdateType>, CacheEntry<FactorioEventUpdateType>>,
|
||||||
DEFAULT_EXPIRATION_DURATION_TICKS: uint,
|
DEFAULT_CACHE_DURATION_TICKS: LuaTable<FactorioEventUpdateType, uint>,
|
||||||
|
DEFAULT_CACHE_DURATION_TICKS2: Record<FactorioEventUpdateType | "default", uint>,
|
||||||
|
MAP_CHUNK_CACHE_DURATION_TICKS: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
if (typeof global.cache == undefined) {
|
if (typeof global.cache == undefined) {
|
||||||
global.cache = new LuaTable<CacheKey<FactorioEventUpdate>, CacheEntry<FactorioEventUpdate>>()
|
global.cache = new LuaTable<CacheKey<FactorioEventUpdateType>, CacheEntry<FactorioEventUpdateType>>()
|
||||||
|
|
||||||
global.DEFAULT_EXPIRATION_DURATION_TICKS = 60 * 60 // ~60 seconds
|
global.DEFAULT_CACHE_DURATION_TICKS = new LuaTable<FactorioEventUpdateType, uint>()
|
||||||
|
global.DEFAULT_CACHE_DURATION_TICKS.set("PLAYER", 30) // 0.5 seconds
|
||||||
|
global.DEFAULT_CACHE_DURATION_TICKS.set("MAP_CHUNK", 60 * 15) // ~ 15 seconds
|
||||||
|
global.DEFAULT_CACHE_DURATION_TICKS.set("ENTITY", 60 * 2) // ~2 seconds
|
||||||
|
|
||||||
|
global.DEFAULT_CACHE_DURATION_TICKS2 = {
|
||||||
|
"PLAYER": 30, // 0.5 seconds
|
||||||
|
"MAP_CHUNK": 60 * 15, // ~ 15 seconds
|
||||||
|
"ENTITY": 60 * 2, // ~2 seconds
|
||||||
|
"default": 60
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the cache data without restarting the 'time-to-emit' countdown, so that the data won't
|
* 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}.
|
* be emitted more than once per {@link CacheEntry.expirationDurationTicks}.
|
||||||
*/
|
*/
|
||||||
export function throttle<T extends FactorioEventUpdate>(
|
export function throttle<TYPE extends FactorioEventUpdateType>(
|
||||||
key: CacheKey<T>,
|
key: CacheKey<TYPE>,
|
||||||
mutate: (data: CacheData<T> | undefined) => void,
|
mutate: CacheDataMutator<TYPE>,
|
||||||
) {
|
) {
|
||||||
update(key, mutate, false)
|
update(key, mutate, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the cache data and reset the 'time-to-emit' countdown, so the data won't be emitted
|
* 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}.
|
* until there's an inactivity gap of {@link CacheEntry.expirationDurationTicks}.
|
||||||
*/
|
*/
|
||||||
export function debounce<T extends FactorioEventUpdate>(
|
export function debounce<TYPE extends FactorioEventUpdateType>(
|
||||||
key: CacheKey<T>,
|
key: CacheKey<TYPE>,
|
||||||
mutate: (data: CacheData<T> | undefined) => void,
|
mutate: CacheDataMutator<TYPE>,
|
||||||
) {
|
) {
|
||||||
update(key, mutate, true)
|
update(key, mutate, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function update<T extends FactorioEventUpdate>(
|
|
||||||
key: CacheKey<T>,
|
function update<TYPE extends FactorioEventUpdateType>(
|
||||||
mutate: (data: CacheData<T> | undefined) => void,
|
key: CacheKey<TYPE>,
|
||||||
|
mutate: CacheDataMutator<TYPE>,
|
||||||
resetLastUpdated: boolean,
|
resetLastUpdated: boolean,
|
||||||
) {
|
) {
|
||||||
const entry = getCacheEntry(key)
|
const entry = getCacheEntry(key) ?? createCacheEntry(key, mutate)
|
||||||
mutate(entry?.data)
|
mutate(entry.data)
|
||||||
if (entry != undefined && resetLastUpdated) {
|
if (resetLastUpdated) {
|
||||||
entry.lastUpdatedTick = game.tick
|
entry.lastUpdatedTick = game.tick
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Usage example: If an urgent update comes in, then the expiration can be reduced so that
|
* Usage example: If an urgent update comes in, then the expiration can be reduced so that
|
||||||
* it's transmitted sooner.
|
* it's transmitted sooner.
|
||||||
*/
|
*/
|
||||||
export function setExpiration(
|
export function setExpiration(
|
||||||
key: CacheKey<any>,
|
key: CacheKey<FactorioEventUpdateType>,
|
||||||
expirationDurationTicks?: uint,
|
expirationDurationTicks?: uint,
|
||||||
) {
|
) {
|
||||||
const entry = getCacheEntry(key)
|
const entry = getCacheEntry(key)
|
||||||
|
|
@ -65,9 +81,22 @@ export namespace EventDataCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCacheEntry<T extends FactorioEventUpdate>(
|
|
||||||
key: CacheKey<T>
|
export function extractExpired(): Array<CacheData<FactorioEventUpdateType>> {
|
||||||
): CacheEntry<T> | undefined {
|
const expiredData: Array<CacheData<FactorioEventUpdateType>> = []
|
||||||
|
for (let [key, entry] of global.cache) {
|
||||||
|
if (entry.isExpired()) {
|
||||||
|
expiredData.push(entry.data)
|
||||||
|
global.cache.delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expiredData
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getCacheEntry<TYPE extends FactorioEventUpdateType>(
|
||||||
|
key: CacheKey<TYPE>
|
||||||
|
): CacheEntry<TYPE> | undefined {
|
||||||
if (!global.cache.has(key)) {
|
if (!global.cache.has(key)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
@ -80,27 +109,57 @@ export namespace EventDataCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateType<T extends FactorioEventUpdate> = T["updateType"]
|
|
||||||
|
|
||||||
/** Only include the non-null properties */
|
function createCacheEntry<TYPE extends FactorioEventUpdateType>(
|
||||||
export type CacheKey<T extends FactorioEventUpdate> = ExcludeNullKeys<T> & {
|
key: CacheKey<TYPE>,
|
||||||
updateType: UpdateType<T>
|
mutate: CacheDataMutator<TYPE>,
|
||||||
}
|
): CacheEntry<TYPE> {
|
||||||
|
// @ts-ignore // TODO remove ts-ignore for "not assignable to type"
|
||||||
/** Make the generated schema objects generic closed-polymorphic */
|
const data: CacheData<TYPE> = {updateType: key.updateType}
|
||||||
export type CacheData<T extends FactorioEventUpdate> = Omit<T, "type"> & {
|
mutate(data)
|
||||||
updateType: UpdateType<T>
|
let entry: CacheEntry<TYPE> = new CacheEntry<TYPE>(data)
|
||||||
|
global.cache.set(key, entry)
|
||||||
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CacheEntry<T extends FactorioEventUpdate> {
|
/** Map a {@link FactorioEventUpdateType} to a {@link FactorioEventUpdate} DTO */
|
||||||
|
type ConvertUpdateType<TYPE extends FactorioEventUpdateType> =
|
||||||
|
TYPE extends "PLAYER" ? PlayerUpdate :
|
||||||
|
TYPE extends "MAP_CHUNK" ? MapChunkUpdate :
|
||||||
|
TYPE extends "ENTITY" ? EntityUpdate :
|
||||||
|
never
|
||||||
|
|
||||||
|
|
||||||
|
/** Only include the non-null properties, and make `updateType` specific */
|
||||||
|
export type CacheKey<TYPE extends FactorioEventUpdateType> =
|
||||||
|
CacheTyped<TYPE>
|
||||||
|
& Omit<ExcludeNullKeys<ConvertUpdateType<TYPE>>, "updateType">
|
||||||
|
|
||||||
|
|
||||||
|
/** Exclude {@link CacheKey} fields, and make `updateType` specific */
|
||||||
|
export type CacheData<TYPE extends FactorioEventUpdateType> =
|
||||||
|
CacheTyped<TYPE>
|
||||||
|
& NonNullableKeys<Partial<Omit<ConvertUpdateType<TYPE>, keyof CacheKey<TYPE> | "updateType">>>
|
||||||
|
|
||||||
|
|
||||||
|
export type CacheTyped<TYPE extends FactorioEventUpdateType> = {
|
||||||
|
readonly updateType: TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type CacheDataMutator<TYPE extends FactorioEventUpdateType> =
|
||||||
|
(data: CacheData<TYPE>) => void
|
||||||
|
|
||||||
|
|
||||||
|
class CacheEntry<TYPE extends FactorioEventUpdateType> {
|
||||||
lastUpdatedTick: uint = game.tick
|
lastUpdatedTick: uint = game.tick
|
||||||
expirationDurationTicks?: uint
|
expirationDurationTicks?: uint
|
||||||
|
|
||||||
data: CacheData<T>
|
data: CacheData<TYPE>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
data: CacheData<T>,
|
data: CacheData<TYPE>,
|
||||||
expirationDurationTicks?: uint,
|
expirationDurationTicks?: uint,
|
||||||
) {
|
) {
|
||||||
this.data = data
|
this.data = data
|
||||||
|
|
@ -110,16 +169,18 @@ export namespace EventDataCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
isExpired(): boolean {
|
isExpired(): boolean {
|
||||||
return game.tick -
|
const cacheDuration =
|
||||||
this.lastUpdatedTick >
|
this.expirationDurationTicks ?? global.DEFAULT_CACHE_DURATION_TICKS2[this.data.updateType]
|
||||||
(this.expirationDurationTicks ?? global.DEFAULT_EXPIRATION_DURATION_TICKS)
|
|
||||||
|
return game.tick - this.lastUpdatedTick > cacheDuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isType<T extends FactorioEventUpdate>(
|
|
||||||
data: CacheEntry<T>,
|
function isType<TYPE extends FactorioEventUpdateType>(
|
||||||
updateType: UpdateType<T>,
|
data: CacheEntry<TYPE>,
|
||||||
): data is CacheEntry<T> {
|
updateType: TYPE,
|
||||||
|
): data is CacheEntry<TYPE> {
|
||||||
return data.data.updateType == updateType
|
return data.data.updateType == updateType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,7 +190,7 @@ export namespace EventDataCache {
|
||||||
// type: string
|
// type: string
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// interface Update<T extends FactorioEventUpdateType> {
|
// interface Update<TYPE extends FactorioEventUpdateTypeType> {
|
||||||
// type: T
|
// type: T
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
|
|
||||||
52
modules/events-mod/src/main/typescript/main/cache/cacheChecker.ts
vendored
Normal file
52
modules/events-mod/src/main/typescript/main/cache/cacheChecker.ts
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import {EventDataCache} from "./EventDataCache";
|
||||||
|
import {emitPacket} from "../emitKafkatorioPacket";
|
||||||
|
// import { global } from "../global";
|
||||||
|
|
||||||
|
/** Every 0.1 seconds */
|
||||||
|
script.on_nth_tick(6, (nthTick: NthTickEventData) => {
|
||||||
|
|
||||||
|
const cachedEvents: Array<EventDataCache.CacheData<any>> = EventDataCache.extractExpired()
|
||||||
|
|
||||||
|
for (const event of cachedEvents) {
|
||||||
|
|
||||||
|
const packet: FactorioEventUpdatePacket = {
|
||||||
|
tick: nthTick.tick,
|
||||||
|
modVersion: global.MOD_VERSION,
|
||||||
|
packetType: "UPDATE",
|
||||||
|
update: event,
|
||||||
|
}
|
||||||
|
|
||||||
|
emitPacket(packet)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
//
|
||||||
|
// function createEntityUpdate(cacheKey: EntityCacheKey, events: EventName[]): EntityUpdate | null {
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// interface EntityUpdate {
|
||||||
|
// name: string
|
||||||
|
//
|
||||||
|
// unit_number: uint
|
||||||
|
//
|
||||||
|
// unhandledEvents?: string[]
|
||||||
|
//
|
||||||
|
// chunkPosition: MapChunkPosition
|
||||||
|
// graphics_variation?: uint8
|
||||||
|
// health?: float
|
||||||
|
// isActive?: boolean
|
||||||
|
// isRotatable?: boolean
|
||||||
|
// last_user?: uint
|
||||||
|
// localised_description?: string
|
||||||
|
// localised_name?: string
|
||||||
|
// prototype?: string
|
||||||
|
//
|
||||||
|
// // ghost_localised_description?: string
|
||||||
|
// // ghost_localised_name?: string
|
||||||
|
// // ghost_name?: string
|
||||||
|
// // ghost_prototype?: string
|
||||||
|
// // ghost_unit_number?: uint
|
||||||
|
// }
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {Converters} from "../events/converters";
|
import {Converters} from "../events/converters";
|
||||||
import {emitPacket} from "../emitKafkatorioPacket";
|
import {emitPacket} from "../emitKafkatorioPacket";
|
||||||
|
// import { global } from "../global";
|
||||||
|
|
||||||
export function emitPrototypes() {
|
export function emitPrototypes() {
|
||||||
emitPacket<FactorioPrototypes>({
|
emitPacket<FactorioPrototypes>({
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
/** Emit a serialised KafkatorioPacket */
|
/** Emit a serialised KafkatorioPacket */
|
||||||
export function emitPacket<T extends KafkatorioPacket>(packet: T) {
|
export function emitPacket<T extends KafkatorioPacket>(packet: T) {
|
||||||
let data = game.table_to_json(packet)
|
let data = game.table_to_json(packet)
|
||||||
// localised_print(`KafkatorioPacket: ${packet.packetType}`)
|
|
||||||
localised_print(`KafkatorioPacket: ${data}`)
|
localised_print(`KafkatorioPacket: ${data}`)
|
||||||
// rcon.print(`KafkatorioPacket: ${data}`)
|
// rcon.print(`KafkatorioPacket: ${data}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
export namespace Converters {
|
export namespace Converters {
|
||||||
|
|
||||||
export function playerToTable(player: LuaPlayer): PlayerData {
|
// export function playerToTable(player: LuaPlayer): PlayerData {
|
||||||
let charIds = entitiesToUnitNumbers(player.get_associated_characters())
|
// let charIds = entitiesToUnitNumbers(player.get_associated_characters())
|
||||||
return {
|
// return {
|
||||||
objectName: player.object_name,
|
// objectName: player.object_name,
|
||||||
|
//
|
||||||
name: player.name,
|
// name: player.name,
|
||||||
characterUnitNumber: player.character?.unit_number ?? null,
|
// characterUnitNumber: player.character?.unit_number ?? null,
|
||||||
associatedCharactersUnitNumbers: charIds,
|
// associatedCharactersUnitNumbers: charIds,
|
||||||
position: mapEntityPosition(player.position),
|
// position: mapEntityPosition(player.position),
|
||||||
colour: mapColour(player.color),
|
// colour: mapColour(player.color),
|
||||||
chatColour: mapColour(player.chat_color),
|
// chatColour: mapColour(player.chat_color),
|
||||||
lastOnline: player.last_online
|
// lastOnline: player.last_online
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function entityToTable(entity: LuaEntity): EntityData {
|
export function entityToTable(entity: LuaEntity): EntityData {
|
||||||
|
|
||||||
|
|
@ -28,9 +28,10 @@ export namespace Converters {
|
||||||
active: entity.active,
|
active: entity.active,
|
||||||
health: entity.health ?? null,
|
health: entity.health ?? null,
|
||||||
healthRatio: entity.get_health_ratio(),
|
healthRatio: entity.get_health_ratio(),
|
||||||
surfaceIndex: entity.surface.index,
|
surface: entity.surface.index,
|
||||||
unitNumber: entity.unit_number ?? null,
|
unitNumber: entity.unit_number ?? null,
|
||||||
position: mapEntityPosition(entity.position),
|
position: mapEntityPosition(entity.position),
|
||||||
|
force: entity.force.index,
|
||||||
|
|
||||||
// player data
|
// player data
|
||||||
playerIndex: player?.index ?? null
|
playerIndex: player?.index ?? null
|
||||||
|
|
@ -103,6 +104,10 @@ export namespace Converters {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertTiles(tiles: LuaTile[]): MapTile[] {
|
||||||
|
return tiles.map((tile: LuaTile) => convertTile(tile))
|
||||||
|
}
|
||||||
|
|
||||||
export function convertBoundingBox(bb: BoundingBoxRead): MapBoundingBox {
|
export function convertBoundingBox(bb: BoundingBoxRead): MapBoundingBox {
|
||||||
return {
|
return {
|
||||||
topLeft: mapTilePosition(bb.left_top),
|
topLeft: mapTilePosition(bb.left_top),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
function isEventType<ED extends EventData | CustomInputEvent>(
|
||||||
|
eventData: EventData,
|
||||||
|
type: ED extends CustomInputEvent ? string : EventId<ED>
|
||||||
|
): eventData is ED {
|
||||||
|
let id: EventId<EventData> | string = eventData.name
|
||||||
|
return id == type
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import {Converters} from "./converters"
|
import {Converters} from "./converters"
|
||||||
import {emitEvent} from "./emitEvent";
|
import {emitEvent} from "./emitEvent";
|
||||||
|
|
||||||
export function handlePlayerUpdate(tick: uint, eventType: string, playerIndex: uint) {
|
// export function handlePlayerUpdate(tick: uint, eventType: string, playerIndex: uint) {
|
||||||
let player: LuaPlayer = game.players[playerIndex]
|
// let player: LuaPlayer = game.players[playerIndex]
|
||||||
let table = Converters.playerToTable(player)
|
// let table = Converters.playerToTable(player)
|
||||||
|
//
|
||||||
emitEvent(table, tick, eventType)
|
// emitEvent(table, tick, eventType)
|
||||||
|
//
|
||||||
handleCharactersEvent(tick, eventType, playerIndex)
|
// handleCharactersEvent(tick, eventType, playerIndex)
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function handleCharactersEvent(tick: uint, eventType: string, playerIndex: uint) {
|
export function handleCharactersEvent(tick: uint, eventType: string, playerIndex: uint) {
|
||||||
|
|
||||||
|
|
|
||||||
213
modules/events-mod/src/main/typescript/main/events/mapEvents.ts
Normal file
213
modules/events-mod/src/main/typescript/main/events/mapEvents.ts
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
import {EventDataCache} from "../cache/EventDataCache";
|
||||||
|
import CacheKey = EventDataCache.CacheKey;
|
||||||
|
import CacheData = EventDataCache.CacheData;
|
||||||
|
|
||||||
|
|
||||||
|
type MapChunkUpdater = (data: CacheData<"MAP_CHUNK">) => void
|
||||||
|
|
||||||
|
|
||||||
|
function mapTilesUpdateDebounce(
|
||||||
|
surface: LuaSurface | undefined,
|
||||||
|
chunkPosition: ChunkPositionTable,
|
||||||
|
tiles: TileRead[],
|
||||||
|
updater?: MapChunkUpdater
|
||||||
|
) {
|
||||||
|
if (surface == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
EventDataCache.debounce<"MAP_CHUNK">(
|
||||||
|
{
|
||||||
|
surfaceIndex: surface.index,
|
||||||
|
chunkPosition: chunkPosition,
|
||||||
|
updateType: "MAP_CHUNK",
|
||||||
|
},
|
||||||
|
data => {
|
||||||
|
if (data.tiles == undefined) {
|
||||||
|
data.tiles = new Map<MapTilePosition, string>()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tile of tiles) {
|
||||||
|
const position: MapTilePosition = {x: tile.position.x, y: tile.position.y}
|
||||||
|
data.tiles.set(position, tile.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updater != undefined) {
|
||||||
|
updater(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getTiles(
|
||||||
|
surface: LuaSurface | undefined,
|
||||||
|
area: BoundingBoxTable
|
||||||
|
): LuaTile[] {
|
||||||
|
return surface?.find_tiles_filtered({
|
||||||
|
area: area,
|
||||||
|
// collision_mask: ["ground-tile", "water-tile"]
|
||||||
|
}
|
||||||
|
) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSurface(surfaceIndex: uint): LuaSurface | undefined {
|
||||||
|
return game.surfaces[surfaceIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_chunk_generated,
|
||||||
|
(e: OnChunkGeneratedEvent) => {
|
||||||
|
let tiles = getTiles(e.surface, e.area)
|
||||||
|
mapTilesUpdateDebounce(e.surface, e.position, tiles)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_chunk_charted,
|
||||||
|
(e: OnChunkChartedEvent) => {
|
||||||
|
|
||||||
|
const surface = getSurface(e.surface_index)
|
||||||
|
if (surface == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let tiles = getTiles(surface, e.area)
|
||||||
|
mapTilesUpdateDebounce(surface, e.position, tiles, (data => {
|
||||||
|
data.force = e.force.index
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.script_raised_set_tiles,
|
||||||
|
(e: ScriptRaisedSetTilesEvent) => {
|
||||||
|
|
||||||
|
const surface = getSurface(e.surface_index)
|
||||||
|
if (surface == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupedTiles = groupTiles(e.tiles)
|
||||||
|
|
||||||
|
for (const [chunkPos, tiles] of groupedTiles) {
|
||||||
|
mapTilesUpdateDebounce(surface, chunkPos, tiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
function groupTiles(tiles: TileRead[]): Map<MapChunkPosition, TileRead[]> {
|
||||||
|
|
||||||
|
const mapChunkPositionToTiles = new Map<MapChunkPosition, TileRead[]>()
|
||||||
|
for (const tile of tiles) {
|
||||||
|
const chunkPosition = {
|
||||||
|
x: math.floor((tile.position.x / 32)),
|
||||||
|
y: math.floor((tile.position.y / 32)),
|
||||||
|
}
|
||||||
|
if (!mapChunkPositionToTiles.has(chunkPosition)) {
|
||||||
|
mapChunkPositionToTiles.set(chunkPosition, [])
|
||||||
|
}
|
||||||
|
mapChunkPositionToTiles.get(chunkPosition)?.push(tile)
|
||||||
|
}
|
||||||
|
return mapChunkPositionToTiles
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertOldPosition(
|
||||||
|
oldTiles: OldTileAndPosition[],
|
||||||
|
placedTile: LuaTilePrototype,
|
||||||
|
): TileRead[] {
|
||||||
|
|
||||||
|
return oldTiles.map((tile) => {
|
||||||
|
return {
|
||||||
|
position: {x: tile.position.x, y: tile.position.y},
|
||||||
|
name: placedTile.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBuildTileEvent(event: OnPlayerBuiltTileEvent | OnRobotBuiltTileEvent) {
|
||||||
|
|
||||||
|
const surface = getSurface(event.surface_index)
|
||||||
|
if (surface == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tiles: TileRead[] = convertOldPosition(event.tiles, event.tile)
|
||||||
|
const groupedTiles = groupTiles(tiles)
|
||||||
|
|
||||||
|
for (const [chunkPos, tiles] of groupedTiles) {
|
||||||
|
mapTilesUpdateDebounce(
|
||||||
|
surface,
|
||||||
|
chunkPos,
|
||||||
|
tiles,
|
||||||
|
(data => {
|
||||||
|
if (isEventType(event, defines.events.on_player_built_tile)) {
|
||||||
|
data.player = event.player_index
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isEventType(event, defines.events.on_robot_built_tile)) {
|
||||||
|
data.robot = {
|
||||||
|
unitNumber: event.robot?.unit_number ?? null,
|
||||||
|
name: event.robot.name,
|
||||||
|
type: event.robot.type,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// this line will not compile if the previous 'if' statements are not exhaustive
|
||||||
|
// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols
|
||||||
|
const exhaustiveCheck: never = event
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
script.on_event(defines.events.on_player_built_tile, (e: OnPlayerBuiltTileEvent) => onBuildTileEvent(e))
|
||||||
|
script.on_event(defines.events.on_robot_built_tile, (e: OnRobotBuiltTileEvent) => onBuildTileEvent(e))
|
||||||
|
|
||||||
|
// script.on_event(
|
||||||
|
// defines.events.on_player_mined_tile,
|
||||||
|
// (e: OnPlayerMinedTileEvent) => {
|
||||||
|
// // need to group tiles by ChunkPosition...
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// script.on_event(
|
||||||
|
// defines.events.on_robot_mined_tile,
|
||||||
|
// (e: OnRobotMinedTileEvent) => {
|
||||||
|
// // need to group tiles by ChunkPosition...
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_pre_chunk_deleted,
|
||||||
|
(e: OnPreChunkDeletedEvent) => {
|
||||||
|
|
||||||
|
const surface = getSurface(e.surface_index)
|
||||||
|
if (surface == undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const position of e.positions) {
|
||||||
|
const key: CacheKey<"MAP_CHUNK"> = {
|
||||||
|
surfaceIndex: surface.index,
|
||||||
|
chunkPosition: position,
|
||||||
|
updateType: "MAP_CHUNK",
|
||||||
|
}
|
||||||
|
|
||||||
|
EventDataCache.debounce<"MAP_CHUNK">(
|
||||||
|
key,
|
||||||
|
data => {
|
||||||
|
data.isDeleted = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// deletion is important - emit ASAP
|
||||||
|
EventDataCache.setExpiration(key, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
import {EventDataCache} from "../cache/EventDataCache";
|
||||||
|
import {Converters} from "./converters";
|
||||||
|
import CacheData = EventDataCache.CacheData;
|
||||||
|
|
||||||
|
type PlayerUpdater = (player: LuaPlayer, data: CacheData<"PLAYER">) => void
|
||||||
|
|
||||||
|
function playerUpdateThrottle(
|
||||||
|
playerIndex: uint,
|
||||||
|
mutate: PlayerUpdater,
|
||||||
|
) {
|
||||||
|
EventDataCache.throttle<"PLAYER">(
|
||||||
|
{index: playerIndex, updateType: "PLAYER"},
|
||||||
|
(data => {
|
||||||
|
const player = game.players[playerIndex]
|
||||||
|
if (player != undefined) {
|
||||||
|
mutate(player, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerUpdateDebounce(playerIndex: uint, mutate: PlayerUpdater) {
|
||||||
|
EventDataCache.debounce<"PLAYER">(
|
||||||
|
{index: playerIndex, updateType: "PLAYER"},
|
||||||
|
(data => {
|
||||||
|
const player = game.players[playerIndex]
|
||||||
|
mutate(player, data)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerOnlineInfo(player: LuaPlayer, data: CacheData<"PLAYER">) {
|
||||||
|
data.lastOnline = player.last_online
|
||||||
|
data.onlineTime = player.online_time
|
||||||
|
data.afkTime = player.afk_time
|
||||||
|
data.isConnected = player.connected
|
||||||
|
}
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_player_joined_game,
|
||||||
|
(e: OnPlayerJoinedGameEvent) => {
|
||||||
|
playerUpdateThrottle(
|
||||||
|
e.player_index,
|
||||||
|
(player, data) => {
|
||||||
|
data.isAdmin = player.admin
|
||||||
|
data.characterUnitNumber = player.character?.unit_number
|
||||||
|
data.chatColour = Converters.mapColour(player.chat_color)
|
||||||
|
data.colour = Converters.mapColour(player.color)
|
||||||
|
data.forceIndex = player.force.index
|
||||||
|
data.name = player.name
|
||||||
|
data.isShowOnMap = player.show_on_map
|
||||||
|
data.isSpectator = player.spectator
|
||||||
|
data.tag = player.tag
|
||||||
|
playerOnlineInfo(player, data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_player_changed_position,
|
||||||
|
(e: OnPlayerChangedPositionEvent) => {
|
||||||
|
playerUpdateThrottle(
|
||||||
|
e.player_index,
|
||||||
|
(player, data) => {
|
||||||
|
data.position = player.position
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_player_changed_surface,
|
||||||
|
(e: OnPlayerChangedSurfaceEvent) => {
|
||||||
|
playerUpdateThrottle(
|
||||||
|
e.player_index,
|
||||||
|
(player, data) => {
|
||||||
|
data.position = player.position
|
||||||
|
data.surfaceIndex = player.surface.index
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_player_died,
|
||||||
|
(e: OnPlayerDiedEvent) => {
|
||||||
|
playerUpdateThrottle(
|
||||||
|
e.player_index,
|
||||||
|
(player, data) => {
|
||||||
|
data.ticksToRespawn = player.ticks_to_respawn
|
||||||
|
playerOnlineInfo(player, data)
|
||||||
|
if (e.cause != undefined) {
|
||||||
|
data.diedCause = {
|
||||||
|
unitNumber: e.cause.unit_number ?? null,
|
||||||
|
name: e.cause.name,
|
||||||
|
type: e.cause.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_player_banned,
|
||||||
|
(e: OnPlayerBannedEvent) => {
|
||||||
|
if (e.player_index != undefined) {
|
||||||
|
playerUpdateThrottle(
|
||||||
|
e.player_index,
|
||||||
|
(player, data) => {
|
||||||
|
data.bannedReason = e.reason
|
||||||
|
playerOnlineInfo(player, data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_player_unbanned,
|
||||||
|
(e: OnPlayerUnbannedEvent) => {
|
||||||
|
if (e.player_index != undefined) {
|
||||||
|
playerUpdateThrottle(
|
||||||
|
e.player_index,
|
||||||
|
(player, data) => {
|
||||||
|
data.bannedReason = e.reason
|
||||||
|
playerOnlineInfo(player, data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_player_kicked,
|
||||||
|
(e: OnPlayerKickedEvent) => {
|
||||||
|
playerUpdateThrottle(
|
||||||
|
e.player_index,
|
||||||
|
(player, data) => {
|
||||||
|
data.kickedReason = e.reason
|
||||||
|
playerOnlineInfo(player, data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
const disconnectReasons = new LuaTable<defines.disconnect_reason, keyof typeof defines.disconnect_reason>()
|
||||||
|
for (const [name, disconnectId] of pairs(defines.disconnect_reason)) {
|
||||||
|
disconnectReasons.set(disconnectId, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_pre_player_left_game,
|
||||||
|
(e: OnPrePlayerLeftGameEvent) => {
|
||||||
|
playerUpdateThrottle(
|
||||||
|
e.player_index,
|
||||||
|
(player, data) => {
|
||||||
|
data.disconnectReason = disconnectReasons.get(e.reason)
|
||||||
|
playerOnlineInfo(player, data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
script.on_event(
|
||||||
|
defines.events.on_player_removed,
|
||||||
|
(e: OnPlayerRemovedEvent) => {
|
||||||
|
playerUpdateThrottle(
|
||||||
|
e.player_index,
|
||||||
|
(player, data) => {
|
||||||
|
data.isRemoved = true
|
||||||
|
playerOnlineInfo(player, data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -2,12 +2,12 @@ import {
|
||||||
handleChunkUpdate,
|
handleChunkUpdate,
|
||||||
handleConsoleChat,
|
handleConsoleChat,
|
||||||
handleEntityUpdate,
|
handleEntityUpdate,
|
||||||
handlePlayerUpdate,
|
// handlePlayerUpdate,
|
||||||
handleSurfaceUpdate,
|
handleSurfaceUpdate,
|
||||||
handleTilesUpdate
|
handleTilesUpdate
|
||||||
} from "./handlers";
|
} from "./handlers";
|
||||||
import {Queue} from "../queue/queue";
|
import {Queue} from "../queue/queue";
|
||||||
import {EventName} from "../global";
|
import {EventName} from "../types";
|
||||||
|
|
||||||
|
|
||||||
const mapEventIdToName = new LuaTable<defines.Events, EventName>()
|
const mapEventIdToName = new LuaTable<defines.Events, EventName>()
|
||||||
|
|
@ -32,24 +32,24 @@ script.on_event(
|
||||||
defines.events.on_player_mined_entity,
|
defines.events.on_player_mined_entity,
|
||||||
(e: OnPlayerMinedEntityEvent) => {
|
(e: OnPlayerMinedEntityEvent) => {
|
||||||
let eventName = mapEventIdToName.get(e.name)
|
let eventName = mapEventIdToName.get(e.name)
|
||||||
handlePlayerUpdate(e.tick, eventName, e.player_index)
|
// handlePlayerUpdate(e.tick, eventName, e.player_index)
|
||||||
handleEntityUpdate(e.tick, eventName, e.entity)
|
handleEntityUpdate(e.tick, eventName, e.entity)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
script.on_event(
|
// script.on_event(
|
||||||
defines.events.on_player_joined_game,
|
// defines.events.on_player_joined_game,
|
||||||
(e: OnPlayerJoinedGameEvent) => {
|
// (e: OnPlayerJoinedGameEvent) => {
|
||||||
handlePlayerUpdate(e.tick, mapEventIdToName.get(e.name), e.player_index)
|
// handlePlayerUpdate(e.tick, mapEventIdToName.get(e.name), e.player_index)
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
|
|
||||||
script.on_event(
|
// script.on_event(
|
||||||
defines.events.on_player_changed_position,
|
// defines.events.on_player_changed_position,
|
||||||
(e: OnPlayerChangedPositionEvent) => {
|
// (e: OnPlayerChangedPositionEvent) => {
|
||||||
handlePlayerUpdate(e.tick, mapEventIdToName.get(e.name), e.player_index)
|
// handlePlayerUpdate(e.tick, mapEventIdToName.get(e.name), e.player_index)
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
|
|
||||||
script.on_event(
|
script.on_event(
|
||||||
defines.events.on_tick,
|
defines.events.on_tick,
|
||||||
|
|
@ -113,12 +113,3 @@ script.on_event(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
function isEventType<ED extends EventData | CustomInputEvent>(
|
|
||||||
eventData: EventData,
|
|
||||||
type: ED extends CustomInputEvent ? string : EventId<ED>
|
|
||||||
): eventData is ED {
|
|
||||||
let id: EventId<EventData> | string = eventData.name
|
|
||||||
return id == type
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,3 @@ declare const global: {
|
||||||
MOD_VERSION: string
|
MOD_VERSION: string
|
||||||
FACTORIO_VERSION: string
|
FACTORIO_VERSION: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EventName = keyof typeof defines.events
|
|
||||||
|
|
||||||
type NonNullKeys<T> = {
|
|
||||||
[P in keyof T]: null extends Exclude<T[P], undefined> ? never : P
|
|
||||||
}[keyof T];
|
|
||||||
|
|
||||||
type ExcludeNullKeys<T> = Pick<T, NonNullKeys<T>>
|
|
||||||
|
|
|
||||||
17
modules/events-mod/src/main/typescript/main/types.d.ts
vendored
Normal file
17
modules/events-mod/src/main/typescript/main/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
export type EventName = keyof typeof defines.events
|
||||||
|
|
||||||
|
|
||||||
|
/** Get all keys of `TYPE` that are not nullable. */
|
||||||
|
type NonNullKeys<TYPE> = {
|
||||||
|
[KEY in keyof TYPE]: null extends Exclude<TYPE[KEY], undefined> ? never : KEY
|
||||||
|
}[keyof TYPE];
|
||||||
|
|
||||||
|
|
||||||
|
/** Exclude all nullable keys from `TYPE`. */
|
||||||
|
type ExcludeNullKeys<TYPE> = Pick<TYPE, NonNullKeys<TYPE>>
|
||||||
|
|
||||||
|
|
||||||
|
/** Make all keys of `TYPE` non-nullable. */
|
||||||
|
type NonNullableKeys<TYPE> = {
|
||||||
|
[KEY in keyof TYPE]: NonNullable<TYPE[KEY]>;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue