package app import ( "context" "log" "log/slog" "os" "os/signal" "sync" "syscall" "github.com/akyaiy/GoSally-mvp/internal/core/corestate" "github.com/akyaiy/GoSally-mvp/internal/core/update" "github.com/akyaiy/GoSally-mvp/internal/engine/config" ) type AppContract interface { InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX)) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)) CallFallback(ctx context.Context) } type App struct { initHooks []func(cs *corestate.CoreState, x *AppX) runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error fallback func(ctx context.Context, cs *corestate.CoreState, x *AppX) Corestate *corestate.CoreState AppX *AppX fallbackOnce sync.Once } type AppX struct { Config *config.Compositor Log *log.Logger SLog *slog.Logger Updated *update.Updater } func New() AppContract { return &App{ AppX: &AppX{ Log: log.Default(), }, Corestate: &corestate.CoreState{}, } } func (a *App) InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX)) { a.initHooks = append(a.initHooks, fn...) } func (a *App) Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)) { a.fallback = fn } func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) { a.runHook = fn for _, hook := range a.initHooks { hook(a.Corestate, a.AppX) } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) defer stop() defer func() { if r := recover(); r != nil { a.AppX.Log.Printf("PANIC recovered: %v", r) if a.fallback != nil { a.fallback(ctx, a.Corestate, a.AppX) } os.Exit(1) } }() var runErr error if a.runHook != nil { runErr = a.runHook(ctx, a.Corestate, a.AppX) } if runErr != nil { a.AppX.Log.Fatalf("fatal in Run: %v", runErr) } } func (a *App) CallFallback(ctx context.Context) { a.fallbackOnce.Do(func() { if a.fallback != nil { a.fallback(ctx, a.Corestate, a.AppX) } }) }