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 {
|
||||
|
||||
|
||||
declare const global: {
|
||||
cache: LuaTable<CacheKey<FactorioEventUpdate>, CacheEntry<FactorioEventUpdate>>,
|
||||
DEFAULT_EXPIRATION_DURATION_TICKS: uint,
|
||||
cache: LuaTable<CacheKey<FactorioEventUpdateType>, CacheEntry<FactorioEventUpdateType>>,
|
||||
DEFAULT_CACHE_DURATION_TICKS: LuaTable<FactorioEventUpdateType, uint>,
|
||||
DEFAULT_CACHE_DURATION_TICKS2: Record<FactorioEventUpdateType | "default", uint>,
|
||||
MAP_CHUNK_CACHE_DURATION_TICKS: uint,
|
||||
}
|
||||
|
||||
|
||||
export function init() {
|
||||
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
|
||||
* be emitted more than once per {@link CacheEntry.expirationDurationTicks}.
|
||||
*/
|
||||
export function throttle<T extends FactorioEventUpdate>(
|
||||
key: CacheKey<T>,
|
||||
mutate: (data: CacheData<T> | undefined) => void,
|
||||
export function throttle<TYPE extends FactorioEventUpdateType>(
|
||||
key: CacheKey<TYPE>,
|
||||
mutate: CacheDataMutator<TYPE>,
|
||||
) {
|
||||
update(key, mutate, false)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
export function debounce<T extends FactorioEventUpdate>(
|
||||
key: CacheKey<T>,
|
||||
mutate: (data: CacheData<T> | undefined) => void,
|
||||
export function debounce<TYPE extends FactorioEventUpdateType>(
|
||||
key: CacheKey<TYPE>,
|
||||
mutate: CacheDataMutator<TYPE>,
|
||||
) {
|
||||
update(key, mutate, true)
|
||||
}
|
||||
|
||||
function update<T extends FactorioEventUpdate>(
|
||||
key: CacheKey<T>,
|
||||
mutate: (data: CacheData<T> | undefined) => void,
|
||||
|
||||
function update<TYPE extends FactorioEventUpdateType>(
|
||||
key: CacheKey<TYPE>,
|
||||
mutate: CacheDataMutator<TYPE>,
|
||||
resetLastUpdated: boolean,
|
||||
) {
|
||||
const entry = getCacheEntry(key)
|
||||
mutate(entry?.data)
|
||||
if (entry != undefined && resetLastUpdated) {
|
||||
const entry = getCacheEntry(key) ?? createCacheEntry(key, mutate)
|
||||
mutate(entry.data)
|
||||
if (resetLastUpdated) {
|
||||
entry.lastUpdatedTick = game.tick
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Usage example: If an urgent update comes in, then the expiration can be reduced so that
|
||||
* it's transmitted sooner.
|
||||
*/
|
||||
export function setExpiration(
|
||||
key: CacheKey<any>,
|
||||
key: CacheKey<FactorioEventUpdateType>,
|
||||
expirationDurationTicks?: uint,
|
||||
) {
|
||||
const entry = getCacheEntry(key)
|
||||
|
|
@ -65,9 +81,22 @@ export namespace EventDataCache {
|
|||
}
|
||||
}
|
||||
|
||||
function getCacheEntry<T extends FactorioEventUpdate>(
|
||||
key: CacheKey<T>
|
||||
): CacheEntry<T> | undefined {
|
||||
|
||||
export function extractExpired(): Array<CacheData<FactorioEventUpdateType>> {
|
||||
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)) {
|
||||
return undefined
|
||||
}
|
||||
|
|
@ -80,27 +109,57 @@ export namespace EventDataCache {
|
|||
}
|
||||
}
|
||||
|
||||
type UpdateType<T extends FactorioEventUpdate> = T["updateType"]
|
||||
|
||||
/** Only include the non-null properties */
|
||||
export type CacheKey<T extends FactorioEventUpdate> = ExcludeNullKeys<T> & {
|
||||
updateType: UpdateType<T>
|
||||
}
|
||||
|
||||
/** Make the generated schema objects generic closed-polymorphic */
|
||||
export type CacheData<T extends FactorioEventUpdate> = Omit<T, "type"> & {
|
||||
updateType: UpdateType<T>
|
||||
function createCacheEntry<TYPE extends FactorioEventUpdateType>(
|
||||
key: CacheKey<TYPE>,
|
||||
mutate: CacheDataMutator<TYPE>,
|
||||
): CacheEntry<TYPE> {
|
||||
// @ts-ignore // TODO remove ts-ignore for "not assignable to type"
|
||||
const data: CacheData<TYPE> = {updateType: key.updateType}
|
||||
mutate(data)
|
||||
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
|
||||
expirationDurationTicks?: uint
|
||||
|
||||
data: CacheData<T>
|
||||
data: CacheData<TYPE>
|
||||
|
||||
constructor(
|
||||
data: CacheData<T>,
|
||||
data: CacheData<TYPE>,
|
||||
expirationDurationTicks?: uint,
|
||||
) {
|
||||
this.data = data
|
||||
|
|
@ -110,16 +169,18 @@ export namespace EventDataCache {
|
|||
}
|
||||
|
||||
isExpired(): boolean {
|
||||
return game.tick -
|
||||
this.lastUpdatedTick >
|
||||
(this.expirationDurationTicks ?? global.DEFAULT_EXPIRATION_DURATION_TICKS)
|
||||
const cacheDuration =
|
||||
this.expirationDurationTicks ?? global.DEFAULT_CACHE_DURATION_TICKS2[this.data.updateType]
|
||||
|
||||
return game.tick - this.lastUpdatedTick > cacheDuration
|
||||
}
|
||||
}
|
||||
|
||||
function isType<T extends FactorioEventUpdate>(
|
||||
data: CacheEntry<T>,
|
||||
updateType: UpdateType<T>,
|
||||
): data is CacheEntry<T> {
|
||||
|
||||
function isType<TYPE extends FactorioEventUpdateType>(
|
||||
data: CacheEntry<TYPE>,
|
||||
updateType: TYPE,
|
||||
): data is CacheEntry<TYPE> {
|
||||
return data.data.updateType == updateType
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +190,7 @@ export namespace EventDataCache {
|
|||
// type: string
|
||||
// }
|
||||
//
|
||||
// interface Update<T extends FactorioEventUpdateType> {
|
||||
// interface Update<TYPE extends FactorioEventUpdateTypeType> {
|
||||
// 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 {emitPacket} from "../emitKafkatorioPacket";
|
||||
// import { global } from "../global";
|
||||
|
||||
export function emitPrototypes() {
|
||||
emitPacket<FactorioPrototypes>({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/** Emit a serialised KafkatorioPacket */
|
||||
export function emitPacket<T extends KafkatorioPacket>(packet: T) {
|
||||
let data = game.table_to_json(packet)
|
||||
// localised_print(`KafkatorioPacket: ${packet.packetType}`)
|
||||
localised_print(`KafkatorioPacket: ${data}`)
|
||||
// rcon.print(`KafkatorioPacket: ${data}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
export namespace Converters {
|
||||
|
||||
export function playerToTable(player: LuaPlayer): PlayerData {
|
||||
let charIds = entitiesToUnitNumbers(player.get_associated_characters())
|
||||
return {
|
||||
objectName: player.object_name,
|
||||
|
||||
name: player.name,
|
||||
characterUnitNumber: player.character?.unit_number ?? null,
|
||||
associatedCharactersUnitNumbers: charIds,
|
||||
position: mapEntityPosition(player.position),
|
||||
colour: mapColour(player.color),
|
||||
chatColour: mapColour(player.chat_color),
|
||||
lastOnline: player.last_online
|
||||
}
|
||||
}
|
||||
// export function playerToTable(player: LuaPlayer): PlayerData {
|
||||
// let charIds = entitiesToUnitNumbers(player.get_associated_characters())
|
||||
// return {
|
||||
// objectName: player.object_name,
|
||||
//
|
||||
// name: player.name,
|
||||
// characterUnitNumber: player.character?.unit_number ?? null,
|
||||
// associatedCharactersUnitNumbers: charIds,
|
||||
// position: mapEntityPosition(player.position),
|
||||
// colour: mapColour(player.color),
|
||||
// chatColour: mapColour(player.chat_color),
|
||||
// lastOnline: player.last_online
|
||||
// }
|
||||
// }
|
||||
|
||||
export function entityToTable(entity: LuaEntity): EntityData {
|
||||
|
||||
|
|
@ -28,9 +28,10 @@ export namespace Converters {
|
|||
active: entity.active,
|
||||
health: entity.health ?? null,
|
||||
healthRatio: entity.get_health_ratio(),
|
||||
surfaceIndex: entity.surface.index,
|
||||
surface: entity.surface.index,
|
||||
unitNumber: entity.unit_number ?? null,
|
||||
position: mapEntityPosition(entity.position),
|
||||
force: entity.force.index,
|
||||
|
||||
// player data
|
||||
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 {
|
||||
return {
|
||||
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 {emitEvent} from "./emitEvent";
|
||||
|
||||
export function handlePlayerUpdate(tick: uint, eventType: string, playerIndex: uint) {
|
||||
let player: LuaPlayer = game.players[playerIndex]
|
||||
let table = Converters.playerToTable(player)
|
||||
|
||||
emitEvent(table, tick, eventType)
|
||||
|
||||
handleCharactersEvent(tick, eventType, playerIndex)
|
||||
}
|
||||
// export function handlePlayerUpdate(tick: uint, eventType: string, playerIndex: uint) {
|
||||
// let player: LuaPlayer = game.players[playerIndex]
|
||||
// let table = Converters.playerToTable(player)
|
||||
//
|
||||
// emitEvent(table, tick, eventType)
|
||||
//
|
||||
// handleCharactersEvent(tick, eventType, playerIndex)
|
||||
// }
|
||||
|
||||
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,
|
||||
handleConsoleChat,
|
||||
handleEntityUpdate,
|
||||
handlePlayerUpdate,
|
||||
// handlePlayerUpdate,
|
||||
handleSurfaceUpdate,
|
||||
handleTilesUpdate
|
||||
} from "./handlers";
|
||||
import {Queue} from "../queue/queue";
|
||||
import {EventName} from "../global";
|
||||
import {EventName} from "../types";
|
||||
|
||||
|
||||
const mapEventIdToName = new LuaTable<defines.Events, EventName>()
|
||||
|
|
@ -32,24 +32,24 @@ script.on_event(
|
|||
defines.events.on_player_mined_entity,
|
||||
(e: OnPlayerMinedEntityEvent) => {
|
||||
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)
|
||||
}
|
||||
)
|
||||
|
||||
script.on_event(
|
||||
defines.events.on_player_joined_game,
|
||||
(e: OnPlayerJoinedGameEvent) => {
|
||||
handlePlayerUpdate(e.tick, mapEventIdToName.get(e.name), e.player_index)
|
||||
}
|
||||
)
|
||||
// script.on_event(
|
||||
// defines.events.on_player_joined_game,
|
||||
// (e: OnPlayerJoinedGameEvent) => {
|
||||
// handlePlayerUpdate(e.tick, mapEventIdToName.get(e.name), e.player_index)
|
||||
// }
|
||||
// )
|
||||
|
||||
script.on_event(
|
||||
defines.events.on_player_changed_position,
|
||||
(e: OnPlayerChangedPositionEvent) => {
|
||||
handlePlayerUpdate(e.tick, mapEventIdToName.get(e.name), e.player_index)
|
||||
}
|
||||
)
|
||||
// script.on_event(
|
||||
// defines.events.on_player_changed_position,
|
||||
// (e: OnPlayerChangedPositionEvent) => {
|
||||
// handlePlayerUpdate(e.tick, mapEventIdToName.get(e.name), e.player_index)
|
||||
// }
|
||||
// )
|
||||
|
||||
script.on_event(
|
||||
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
|
||||
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