Files
Halcyon/runner/runner.go
T
2026-09-19 20:21:47 +02:00

81 lines
1.9 KiB
Go

// Package runner orchestrates the once-a-day simulation against the store.
package runner
import (
"context"
"encoding/json"
"fmt"
"time"
"wh/config"
"wh/sim"
"wh/store"
)
const (
MetaConfig = "config"
MetaDay = "day"
MetaMarket = "market"
)
// RunNextDay simulates the next day atomically: everything is committed
// together or not at all, so a crashed run can simply be repeated.
func RunNextDay(ctx context.Context, s *store.Store, cfg config.Config) (sim.DayResult, error) {
var res sim.DayResult
cfgJSON, err := json.Marshal(cfg)
if err != nil {
return res, err
}
err = s.WithTx(ctx, func(q *store.Q) error {
if err := q.LockWorld(ctx); err != nil {
return err
}
// Refuse to continue a world with different parameters.
if prev, ok, err := q.GetMeta(ctx, MetaConfig); err != nil {
return err
} else if ok && prev != string(cfgJSON) {
return fmt.Errorf("config differs from the one this world was created with:\n stored: %s\n current: %s", prev, cfgJSON)
}
st, err := q.LoadState(ctx)
if err != nil {
return err
}
if st == nil {
st = sim.NewState(cfg)
if err := q.SetMeta(ctx, MetaConfig, string(cfgJSON)); err != nil {
return err
}
if err := q.SaveState(ctx, st); err != nil { // day 0 snapshot
return err
}
}
var in sim.DayInput
if in.Launches, err = q.PendingLaunches(ctx); err != nil {
return err
}
if in.Uplinks, err = q.PendingUplinks(ctx); err != nil {
return err
}
if res, err = st.RunDay(cfg, in); err != nil {
return err
}
if err := q.SaveState(ctx, st); err != nil {
return err
}
if err := q.RecordRun(ctx, res, time.Now().UTC().Format(time.RFC3339)); err != nil {
return err
}
if err := q.AfterRun(ctx, st); err != nil {
return err
}
mk, _ := json.Marshal(st.Market.Prices)
if err := q.SetMeta(ctx, MetaMarket, string(mk)); err != nil {
return err
}
return q.SetMeta(ctx, MetaDay, fmt.Sprint(st.Day))
})
return res, err
}