// 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 }