Files
galaxy-game/loader/loader.go
T
2026-03-12 23:45:06 +02:00

115 lines
2.7 KiB
Go

package loader
import (
"context"
"errors"
"fmt"
"galaxy/connector"
mc "galaxy/model/client"
"galaxy/storage"
"plugin"
"time"
"fyne.io/fyne/v2"
)
type ClientInit func(context.Context, storage.UIStorage, connector.UIConnector, fyne.App) (mc.Client, error)
type loader struct {
conn connector.Connector
cli mc.Client
storagePath string
}
const (
clientLibraryFile = "client"
)
var (
checkConnectionTimeout = time.Second * 5
checkVersionTimeout = time.Minute * 60
)
func NewLoader(ctx context.Context, app fyne.App, conn connector.Connector) (*loader, error) {
storagePath, err := initStorage(app)
if err != nil {
return nil, err
}
var s storage.Storage = nil
cli, err := loadClientPlugin(ctx, s, conn, app, "./client.so", "NewClient")
if err != nil {
return nil, err
}
l := &loader{
conn: conn,
cli: cli,
storagePath: storagePath,
}
return l, nil
}
func (l *loader) Run(ctx context.Context) error {
final := make(chan struct{}, 1)
go l.backgroundLoop(ctx, final)
if err := l.cli.Run(); err != nil {
return err
}
final <- struct{}{}
return nil
}
func (l *loader) backgroundLoop(ctx context.Context, final <-chan struct{}) {
checkConnTimer := time.NewTimer(checkConnectionTimeout)
checkVersionTimer := time.NewTimer(checkVersionTimeout)
defer func() {
checkConnTimer.Stop()
checkVersionTimer.Stop()
}()
for {
select {
case <-ctx.Done():
l.cli.Shutdown()
return
case <-final:
return
case <-checkConnTimer.C:
isGood := l.conn.CheckConnection()
l.cli.OnConnection(isGood)
checkConnTimer.Reset(checkConnectionTimeout)
case <-checkVersionTimer.C:
versions, err := l.conn.CheckVersion()
if err != nil {
// propagate error to the UI
} else if latest, ok := latestVersion(versions); ok {
l.downloadVersion(latest)
}
checkVersionTimer.Reset(checkVersionTimeout)
}
}
}
// loadClientPlugin loads a Client implementation from a shared object (.so) file at the specified path.
// It calls the constructor function by name, passing the necessary dependencies, and returns the initialized Client.
func loadClientPlugin(ctx context.Context, s storage.UIStorage, conn connector.UIConnector, app fyne.App, path, name string) (mc.Client, error) {
if path == "" {
return nil, errors.New("no plugin path given")
}
plug, err := plugin.Open(path)
if err != nil {
return nil, fmt.Errorf("open plugin %q: %w", path, err)
}
sym, err := plug.Lookup(name)
if err != nil {
return nil, fmt.Errorf("lookup symbol %q: %w", name, err)
}
initializerPtr, ok := sym.(*ClientInit)
if !ok {
return nil, fmt.Errorf("unexpected type %T; want %T", sym, initializerPtr)
}
return (*initializerPtr)(ctx, s, conn, app)
}