143 lines
2.7 KiB
Go
143 lines
2.7 KiB
Go
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)
|
||
})
|
||
}
|