feat: ticker
This commit is contained in:
@@ -109,8 +109,8 @@ function createEntitiesQuery(): Query {
|
||||
// Export query list
|
||||
export const queryList: readonly Query[] = [
|
||||
createStatsQuery(),
|
||||
createInventoryQuery(),
|
||||
createNearbyBlocksQuery(),
|
||||
createCraftableQuery(),
|
||||
createEntitiesQuery(),
|
||||
// createInventoryQuery(),
|
||||
// createNearbyBlocksQuery(),
|
||||
// createCraftableQuery(),
|
||||
// createEntitiesQuery(),
|
||||
] as const
|
||||
|
||||
@@ -7,16 +7,25 @@ let ctx: BotContext | undefined
|
||||
|
||||
export interface BotContext {
|
||||
bot: Bot
|
||||
botName: string
|
||||
|
||||
components: Map<string, ComponentLifecycle>
|
||||
|
||||
botName: string
|
||||
prompt: {
|
||||
selfPrompt: string
|
||||
}
|
||||
|
||||
memory: {
|
||||
getSummary: () => string
|
||||
}
|
||||
|
||||
status: Map<string, string>
|
||||
|
||||
health: {
|
||||
value: number
|
||||
lastDamageTime: number
|
||||
lastDamageTaken: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface Component {
|
||||
@@ -45,16 +54,52 @@ export function createBot(options: BotOptions): Bot {
|
||||
getSummary: () => '',
|
||||
},
|
||||
status: new Map(),
|
||||
health: {
|
||||
value: 20,
|
||||
lastDamageTime: 0,
|
||||
lastDamageTaken: 0,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.bot.on('error', (err: Error) => {
|
||||
logger.errorWithError('Bot error:', err)
|
||||
ctx.bot.on('health', () => {
|
||||
if (!ctx)
|
||||
return
|
||||
|
||||
logger.withFields({
|
||||
health: ctx.health.value,
|
||||
lastDamageTime: ctx.health.lastDamageTime,
|
||||
lastDamageTaken: ctx.health.lastDamageTaken,
|
||||
previousHealth: ctx.bot.health,
|
||||
}).log('Health updated')
|
||||
|
||||
if (ctx.bot.health < ctx.health.value) {
|
||||
ctx.health.lastDamageTime = Date.now()
|
||||
ctx.health.lastDamageTaken = ctx.health.value - ctx.bot.health
|
||||
}
|
||||
|
||||
ctx.health.value = ctx.bot.health
|
||||
})
|
||||
|
||||
ctx.bot.on('death', () => {
|
||||
logger.error('Bot died')
|
||||
})
|
||||
|
||||
ctx.bot.on('messagestr', () => {
|
||||
|
||||
})
|
||||
|
||||
ctx.bot.on('end', (reason) => {
|
||||
logger.withFields({ reason }).log('Bot ended')
|
||||
})
|
||||
|
||||
ctx.bot.on('kicked', (reason: string) => {
|
||||
logger.withFields({ reason }).error('Bot was kicked')
|
||||
})
|
||||
|
||||
ctx.bot.on('error', (err: Error) => {
|
||||
logger.errorWithError('Bot error:', err)
|
||||
})
|
||||
|
||||
logger.log('Bot created')
|
||||
return ctx.bot
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import process from 'node:process'
|
||||
import process, { exit } from 'node:process'
|
||||
|
||||
import { useLogg } from '@guiiai/logg'
|
||||
|
||||
@@ -10,6 +10,7 @@ import { createStatusComponent } from './components/status'
|
||||
import { createBot, useBot } from './composables/bot'
|
||||
import { botConfig, initEnv } from './composables/config'
|
||||
import { initLogger } from './utils/logger'
|
||||
import { createTicker } from './utils/ticker'
|
||||
|
||||
const logger = useLogg('main').useGlobalConfig()
|
||||
|
||||
@@ -28,15 +29,20 @@ async function main() {
|
||||
registerComponent('command', createCommandComponent)
|
||||
})
|
||||
|
||||
initAgent(ctx)
|
||||
await initAgent(ctx)
|
||||
|
||||
const ticker = createTicker()
|
||||
ticker.on('tick', async ({ delta }) => {
|
||||
logger.log(`Tick ${delta}ms`)
|
||||
})
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
cleanup()
|
||||
process.exit(0)
|
||||
exit(0)
|
||||
})
|
||||
}
|
||||
|
||||
main().catch((err: Error) => {
|
||||
logger.errorWithError('Fatal error', err)
|
||||
process.exit(1)
|
||||
exit(1)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
export interface TickContext {
|
||||
delta: number
|
||||
nextTick: () => Promise<void>
|
||||
}
|
||||
|
||||
export interface TickEventHandlers {
|
||||
tick: (ctx: TickContext) => void
|
||||
}
|
||||
|
||||
export type TickEvents = keyof TickEventHandlers
|
||||
export type TickEventsHandler<K extends TickEvents> = TickEventHandlers[K]
|
||||
|
||||
// This update loop ensures that each update() is called one at a time, even if it takes longer than the interval
|
||||
export function createTicker(options?: { interval?: number }) {
|
||||
const { interval = 300 } = options ?? { interval: 300 }
|
||||
|
||||
let last = Date.now()
|
||||
const tickingCbs: Record<TickEvents, Array<TickEventHandlers[TickEvents]>> = {
|
||||
tick: [],
|
||||
}
|
||||
|
||||
setTimeout(async () => {
|
||||
while (true) {
|
||||
const start = Date.now()
|
||||
const nextTickPromise = new Promise<void>((resolve) => {
|
||||
// Schedule nextTick resolution for after all callbacks complete
|
||||
setImmediate(resolve)
|
||||
})
|
||||
|
||||
// Run all callbacks without awaiting them
|
||||
const callbackPromises = tickingCbs.tick.map(cb => cb({
|
||||
delta: start - last,
|
||||
nextTick: () => nextTickPromise,
|
||||
}))
|
||||
|
||||
// Wait for all callbacks to complete or timeout
|
||||
await Promise.race([
|
||||
Promise.all(callbackPromises),
|
||||
new Promise(resolve =>
|
||||
setTimeout(resolve, interval),
|
||||
),
|
||||
])
|
||||
|
||||
const remaining = interval - (Date.now() - start)
|
||||
if (remaining > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, remaining))
|
||||
}
|
||||
|
||||
last = start
|
||||
}
|
||||
}, interval)
|
||||
|
||||
return {
|
||||
on<K extends TickEvents>(event: K, cb: TickEventsHandler<K>) {
|
||||
tickingCbs[event].push(cb)
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user