diff --git a/box.go b/box.go index f53695f..376a7bf 100644 --- a/box.go +++ b/box.go @@ -24,6 +24,15 @@ func NewCube(pos, size Vec3) (b *Cube) { } } +func NewCubeFromCenter(size Vec3) (b *Cube) { + pos := size + pos.ScaleN(-0.5) + return &Cube{ + P: pos, + S: size, + } +} + func (b *Cube) String() string { return "Cube(pos=" + b.P.String() + ", size=" + b.S.String() + ")" } diff --git a/engine.go b/engine.go index 318dccd..ffda742 100644 --- a/engine.go +++ b/engine.go @@ -98,6 +98,15 @@ func (e *Engine) GetObject(id uuid.UUID) *Object { return e.objects[id] } +func (e *Engine) ForeachObject(cb func(o *Object)) { + e.RLock() + defer e.RUnlock() + + for _, o := range e.objects { + cb(o) + } +} + func (e *Engine) Events() []eventWave { return e.events } @@ -128,15 +137,18 @@ func (e *Engine) tickLocked(wg *sync.WaitGroup, dt float64) { e.RLock() defer e.RUnlock() - wg.Add(len(e.objects)) for _, o := range e.objects { - go func(o *Object) { - defer wg.Done() - o.tick(dt) - }(o) + if o.IsReady() { + wg.Add(1) + go func(o *Object) { + defer wg.Done() + o.tick(dt) + }(o) + } } for _, event := range e.events { if event.Heavy() { + wg.Add(1) go func(event eventWave) { defer wg.Done() event.Tick(dt, e) @@ -151,12 +163,14 @@ func (e *Engine) saveStatusLocked(wg *sync.WaitGroup) { e.Lock() defer e.Unlock() - wg.Add(len(e.objects)) for _, o := range e.objects { - go func(o *Object) { - defer wg.Done() - o.saveStatus() - }(o) + if o.IsReady() { + wg.Add(1) + go func(o *Object) { + defer wg.Done() + o.saveStatus() + }(o) + } } for i := 0; i < len(e.events); { event := e.events[i] diff --git a/event.go b/event.go index ffe7a08..7bab1f2 100644 --- a/event.go +++ b/event.go @@ -156,7 +156,7 @@ func (e *Engine) newGravityWave(sender *Object, center Vec3, f *GravityField, ti g.f = *f g.pos = center g.gone = false - g.c = 1 + g.c.Store(1) maxRadius := -1.0 if e.cfg.MinAccel > 0 { maxRadius = math.Sqrt(G / e.cfg.MinAccel * f.Mass()) @@ -181,7 +181,7 @@ func (e *Engine) newGravityWave(sender *Object, center Vec3, f *GravityField, ti } g.count() r.passedGravity[sender] = g - }, false) + }, true) w.onRemove = g.release w.onBeforeTick = func(e *eventWaveFn) bool { switch { diff --git a/force.go b/force.go index a36e292..b018309 100644 --- a/force.go +++ b/force.go @@ -3,6 +3,7 @@ package molecular import ( "math" "sync" + "sync/atomic" ) const ( @@ -61,7 +62,7 @@ type gravityStatus struct { pos Vec3 gone bool - c int + c atomic.Int32 } var gravityStatusPool = sync.Pool{ @@ -76,21 +77,23 @@ func (s *gravityStatus) FieldAt(pos Vec3) Vec3 { func (s *gravityStatus) clone() (g *gravityStatus) { g = gravityStatusPool.Get().(*gravityStatus) - *g = *s - g.c = 1 + g.f = s.f + g.pos = s.pos + g.gone = false + g.c.Store(1) return } func (s *gravityStatus) count() { - s.c++ + s.c.Add(1) } func (s *gravityStatus) release() { - s.c-- - if s.c < 0 { + c := s.c.Add(-1) + if c < 0 { panic("gravityStatus.count become less than one") } - if s.c == 0 { + if c == 0 { gravityStatusPool.Put(s) } } diff --git a/object.go b/object.go index 6e034b3..a87a1d0 100644 --- a/object.go +++ b/object.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "sync" + "sync/atomic" "github.com/google/uuid" ) @@ -57,8 +58,10 @@ func (s *objStatus) from(a *objStatus) { for k, v := range a.passedGravity { g := s.passedGravity[k] if g != nil { - *g = *v - g.c = 1 + g.f = v.f + g.pos = v.pos + g.gone = false + g.c.Store(1) } else { s.passedGravity[k] = v.clone() } @@ -97,13 +100,14 @@ func (s *objStatus) clone() (a objStatus) { // Object represents an object in the physics engine. type Object struct { sync.RWMutex + ready atomic.Bool e *Engine id uuid.UUID // a v7 UUID typ ObjType blocks []Block objStatus - // lastStatus should only be read during a tick + lastMux sync.RWMutex lastStatus objStatus gtick uint16 @@ -148,6 +152,16 @@ func (o *Object) Engine() *Engine { return o.e } +// IsReady returns true when the object is ready to tick +func (o *Object) IsReady() bool { + return o.ready.Load() +} + +// Ready makes the IsReady returns true +func (o *Object) Ready() { + o.ready.Store(true) +} + // Anchor returns this object's anchor object // If Anchor returns nil, the object is the main anchor func (o *Object) Anchor() *Object { @@ -191,6 +205,9 @@ func (o *Object) SetHeadingVel(v Vec3) { // The new position will be calculated at the same time. // AttachTo must be called inside the object's tick func (o *Object) AttachTo(anchor *Object) { + o.lastMux.RLock() + defer o.lastMux.RUnlock() + if anchor == nil { panic("molecular.Object: new anchor cannot be nil") } @@ -220,7 +237,6 @@ func (o *Object) AttachTo(anchor *Object) { ScaleN(o.e.ReLorentzFactorSq(a.lastStatus.velocity.SqLen())). Add(a.lastStatus.velocity) }) - hneg := o.lastStatus.heading.Negated() v.Sub(v2) o.anchor = anchor o.pos = p @@ -232,12 +248,20 @@ func (o *Object) forEachAnchor(cb func(*Object)) { if o.lastStatus.anchor == nil { return } - cb(o.lastStatus.anchor) - o.lastStatus.anchor.forEachAnchor(cb) + a := o.lastStatus.anchor + + a.lastMux.RLock() + defer a.lastMux.RUnlock() + + cb(a) + a.forEachAnchor(cb) } // AbsPos returns the position relative to the main anchor func (o *Object) AbsPos() (p Vec3) { + o.lastMux.RLock() + defer o.lastMux.RUnlock() + p = o.lastStatus.pos o.forEachAnchor(func(a *Object) { p.Add(a.lastStatus.pos) @@ -246,6 +270,9 @@ func (o *Object) AbsPos() (p Vec3) { } func (o *Object) RotatePos(p *Vec3) *Vec3 { + o.lastMux.RLock() + defer o.lastMux.RUnlock() + if o.anchor != nil { p. Sub(o.lastStatus.gcenter). @@ -264,6 +291,9 @@ func (o *Object) SetVelocity(velocity Vec3) { } func (o *Object) AbsVelocity() (v Vec3) { + o.lastMux.RLock() + defer o.lastMux.RUnlock() + v = o.lastStatus.velocity o.forEachAnchor(func(a *Object) { v. @@ -424,6 +454,8 @@ func (o *Object) tick(dt float64) { func (o *Object) saveStatus() { o.RLock() defer o.RUnlock() + o.lastMux.Lock() + defer o.lastMux.Unlock() o.lastStatus.from(&o.objStatus) }