package txflow import ( "errors" "sync" "sync/atomic" ) type Adapter struct { handle Handle worlds WorldResolver currentTx SafeBox[Tx] mu sync.Mutex pendingTeleport *Position queuedTeleport atomic.Bool processing atomic.Bool } func NewAdapter(handle Handle, worlds WorldResolver) *Adapter { return &Adapter{handle: handle, worlds: worlds} } func (a *Adapter) WithTx(tx Tx, fn func()) { if fn == nil { return } a.currentTx.Set(tx) defer a.currentTx.Set(nil) fn() } func (a *Adapter) WithPlayerTx(fn func(Tx, Entity)) { if a == nil || a.handle == nil || fn == nil { return } if tx := a.currentTx.Get(); tx != nil { if e, ok := a.handle.Entity(tx); ok { fn(tx, e) return } } a.handle.ExecWorld(func(tx Tx, e Entity) { fn(tx, e) }) } func (a *Adapter) Teleport(pos Position) error { if a == nil || a.handle == nil { return errors.New("adapter is nil") } if pos.World == "" { a.WithPlayerTx(func(_ Tx, e Entity) { e.Teleport(pos) }) return nil } target, err := a.worlds.Resolve(pos.World) if err != nil { return err } if target == nil { return errors.New("target world not found") } if tx := a.currentTx.Get(); tx != nil && tx.World() != nil { if tx.World() == target { a.WithPlayerTx(func(_ Tx, e Entity) { e.Teleport(pos) }) return nil } a.queueTeleport(pos) go a.ProcessQueuedTeleport(nil) return nil } a.queueTeleport(pos) a.ProcessQueuedTeleport(nil) return nil } func (a *Adapter) queueTeleport(pos Position) { a.mu.Lock() cp := pos a.pendingTeleport = &cp a.mu.Unlock() a.queuedTeleport.Store(true) } func (a *Adapter) peekQueuedTeleport() *Position { a.mu.Lock() defer a.mu.Unlock() if a.pendingTeleport == nil { return nil } cp := *a.pendingTeleport return &cp } func (a *Adapter) clearQueuedTeleport() { a.mu.Lock() a.pendingTeleport = nil a.mu.Unlock() a.queuedTeleport.Store(false) } func (a *Adapter) HasQueuedTeleport() bool { return a.queuedTeleport.Load() } func (a *Adapter) ProcessQueuedTeleport(tx Tx) { if a == nil || a.handle == nil { return } if !a.processing.CompareAndSwap(false, true) { return } defer a.processing.Store(false) pos := a.peekQueuedTeleport() if pos == nil { return } target, err := a.worlds.Resolve(pos.World) if err != nil || target == nil { return } exec := func(currentTx Tx) { if currentTx == nil || currentTx.World() == nil { return } if e, ok := a.handle.Entity(currentTx); ok { // В примере просто телепортируем. // В реальном движке здесь обычно remove/add entity между мирами. e.Teleport(*pos) a.clearQueuedTeleport() } } if tx != nil { exec(tx) return } target.Exec(func(t Tx) { exec(t) }) }