first commit
This commit is contained in:
142
txflow/adapter.go
Normal file
142
txflow/adapter.go
Normal 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)
|
||||
})
|
||||
}
|
||||
49
txflow/interfaces.go
Normal file
49
txflow/interfaces.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package txflow
|
||||
|
||||
import "sync"
|
||||
|
||||
type Position struct {
|
||||
X, Y, Z float64
|
||||
World string
|
||||
}
|
||||
|
||||
type World interface {
|
||||
Name() string
|
||||
Exec(func(Tx))
|
||||
}
|
||||
|
||||
type Tx interface {
|
||||
World() World
|
||||
}
|
||||
|
||||
type Entity interface {
|
||||
Teleport(Position)
|
||||
}
|
||||
|
||||
type Handle interface {
|
||||
Entity(tx Tx) (Entity, bool)
|
||||
ExecWorld(func(tx Tx, e Entity))
|
||||
}
|
||||
|
||||
type WorldResolver interface {
|
||||
Resolve(name string) (World, error)
|
||||
}
|
||||
|
||||
type SafeBox[T any] struct {
|
||||
mu sync.RWMutex
|
||||
v T
|
||||
}
|
||||
|
||||
func (s *SafeBox[T]) Set(v T) {
|
||||
s.mu.Lock()
|
||||
s.v = v
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *SafeBox[T]) Get() T {
|
||||
s.mu.RLock()
|
||||
v := s.v
|
||||
s.mu.RUnlock()
|
||||
return v
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user