Files
tx-teleport/txflow/adapter.go
2026-02-09 22:03:11 +03:00

143 lines
2.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
})
}