Matou-Garou / convex /init.ts
Jofthomas's picture
Jofthomas HF staff
bulk
ce8b18b
raw
history blame
3.67 kB
import { v } from 'convex/values';
import { internal } from './_generated/api';
import { DatabaseReader, MutationCtx, mutation } from './_generated/server';
import { Descriptions } from '../data/characters';
import * as map from '../data/gentle';
import { insertInput } from './aiTown/insertInput';
import { Id } from './_generated/dataModel';
import { createEngine } from './aiTown/main';
import { ENGINE_ACTION_DURATION, MAX_NPC } from './constants';
import { assertApiKey } from './util/llm';
const init = mutation({
args: {
numAgents: v.optional(v.number()),
},
handler: async (ctx, args) => {
assertApiKey();
const { worldStatus, engine } = await getOrCreateDefaultWorld(ctx);
if (worldStatus.status !== 'running') {
console.warn(
`Engine ${engine._id} is not active! Run "npx convex run testing:resume" to restart it.`,
);
return;
}
const shouldCreate = await shouldCreateAgents(
ctx.db,
worldStatus.worldId,
worldStatus.engineId,
);
if (shouldCreate) {
const toCreate = args.numAgents !== undefined ? args.numAgents : MAX_NPC; //Descriptions.length;
for (let i = 0; i < toCreate; i++) {
await insertInput(ctx, worldStatus.worldId, 'createAgent', {
descriptionIndex: i % Descriptions.length,
type: 'villager',
});
}
}
},
});
export default init;
async function getOrCreateDefaultWorld(ctx: MutationCtx) {
const now = Date.now();
let worldStatus = await ctx.db
.query('worldStatus')
.filter((q) => q.eq(q.field('isDefault'), true))
.unique();
if (worldStatus) {
const engine = (await ctx.db.get(worldStatus.engineId))!;
return { worldStatus, engine };
}
const engineId = await createEngine(ctx);
const engine = (await ctx.db.get(engineId))!;
const worldId = await ctx.db.insert('worlds', {
nextId: 0,
agents: [],
conversations: [],
players: [],
playersInit: [],
// initialize game cycle counter
gameCycle: {
currentTime: 0,
cycleState: 'LobbyState',
cycleIndex: -1,
},
gameVotes: [],
llmVotes: []
});
const worldStatusId = await ctx.db.insert('worldStatus', {
engineId: engineId,
isDefault: true,
lastViewed: now,
status: 'running',
worldId: worldId,
});
worldStatus = (await ctx.db.get(worldStatusId))!;
await ctx.db.insert('maps', {
worldId,
width: map.mapwidth,
height: map.mapheight,
tileSetUrl: map.tilesetpath,
tileSetDimX: map.tilesetpxw,
tileSetDimY: map.tilesetpxh,
tileDim: map.tiledim,
bgTiles: map.bgtiles,
objectTiles: map.objmap,
decorTiles: map.decors,
bgTilesN: map.bgtilesN,
objectTilesN: map.objmapN,
decorTilesN: map.decorsN,
animatedSprites: map.animatedsprites,
});
await ctx.scheduler.runAfter(0, internal.aiTown.main.runStep, {
worldId,
generationNumber: engine.generationNumber,
maxDuration: ENGINE_ACTION_DURATION,
});
return { worldStatus, engine };
}
async function shouldCreateAgents(
db: DatabaseReader,
worldId: Id<'worlds'>,
engineId: Id<'engines'>,
) {
const world = await db.get(worldId);
if (!world) {
throw new Error(`Invalid world ID: ${worldId}`);
}
if (world.agents.length > 0) {
return false;
}
const unactionedJoinInputs = await db
.query('inputs')
.withIndex('byInputNumber', (q) => q.eq('engineId', engineId))
.order('asc')
.filter((q) => q.eq(q.field('name'), 'createAgent'))
.filter((q) => q.eq(q.field('returnValue'), undefined))
.collect();
if (unactionedJoinInputs.length > 0) {
return false;
}
return true;
}