feat: ticker

This commit is contained in:
Neko Ayaka
2025-01-07 01:06:07 +08:00
parent 2575f45306
commit fa3a0fef53
4 changed files with 120 additions and 11 deletions
+4 -4
View File
@@ -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
+48 -3
View File
@@ -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
}
+10 -4
View File
@@ -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)
})
+58
View File
@@ -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)
},
}
}