first commit

This commit is contained in:
2026-02-09 22:03:11 +03:00
commit 2e463a4923
5 changed files with 220 additions and 0 deletions

142
txflow/adapter.go Normal file
View File

@@ -0,0 +1,142 @@
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)
})
}