From 3d0a4f977b413f5c1b00098b9ad50af22c60c357 Mon Sep 17 00:00:00 2001 From: bloeys Date: Thu, 5 Oct 2023 14:34:42 +0400 Subject: [PATCH] Comments and ClearUserData --- main.go | 28 ++++++++++++++++++++++++---- pgo/pgo.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index e5a8d24..38bedb1 100755 --- a/main.go +++ b/main.go @@ -2,12 +2,17 @@ package main import ( "fmt" + "unsafe" "github.com/bloeys/physx-go/pgo" ) func contactHandler(cph pgo.ContactPairHeader) { + // ra1 := cph.GetRigidActors()[0] + // ra2 := cph.GetRigidActors()[1] + // fmt.Printf("Collision! User data 1: %v; User data 2: %v\n", ra1.GetUserData(), ra2.GetUserData()) + // pairs := cph.GetPairs() // for i := 0; i < len(pairs); i++ { @@ -138,6 +143,21 @@ func main() { raycastBuffer := pgo.NewRaycastBuffer(1) defer raycastBuffer.Release() + // Example of correct usage of user data + x := new(int64) + *x = 1095 + ra.SetUserData(unsafe.Pointer(x)) + z := (*int64)(ra.GetUserData()) + fmt.Println("User data:", *z) + + // The rigid actor might get garbage collected after this point (as its no longer used), but that will now cause + // a memory leak and a crash since the user data got pinned (i.e. GC will not move or free it) when we used SetUserData above, but can no longer be unpinned as the runtime.Pinner object will get garbage collected with the rigid actor. + // + // To solve this we have 2 options: + // 1. The pinner is unpinned before it is garbage collected by calling ClearUserData + // 2. The object holding the active pinner (the rigid actor on which SetUserData was used) must remain alive (e.g. by pinning it, putting it in file scope, in a long lived object etc) + ra.ClearUserData() + scene.SetScratchBuffer(4) for { scene.Collide(1 / 50.0) @@ -147,10 +167,10 @@ func main() { scene.RaycastWithHitBuffer(pgo.NewVec3(0, 0, 0), pgo.NewVec3(0, 1, 0), 9, raycastBuffer, 1) if raycastBuffer.HasBlock() { - block := raycastBuffer.GetBlock() - d := block.GetDistance() - pos := block.GetPos() - fmt.Printf("Raycast hit at dist (%v) and post %v\n", d, pos.String()) + // block := raycastBuffer.GetBlock() + // d := block.GetDistance() + // pos := block.GetPos() + // fmt.Printf("Raycast hit at dist (%v) and post %v\n", d, pos.String()) } // fmt.Printf("\nRaycast hit: %v\n", rHit) // fmt.Println("Press enter...") diff --git a/pgo/pgo.go b/pgo/pgo.go index 4f314f1..801071a 100755 --- a/pgo/pgo.go +++ b/pgo/pgo.go @@ -16,6 +16,7 @@ void goOnContactCallback_cgo(void* pairHeader); */ import "C" import ( + "runtime" "unsafe" "github.com/bloeys/gglm/gglm" @@ -660,7 +661,8 @@ type Actor struct { } type RigidActor struct { - cRa C.struct_CPxRigidActor + cRa C.struct_CPxRigidActor + pinner runtime.Pinner } func (ra *RigidActor) SetSimFilterData(fd *FilterData) { @@ -669,7 +671,28 @@ func (ra *RigidActor) SetSimFilterData(fd *FilterData) { // SetUserData sets the void* field on the rigid actor which can be used for any purpose. // For example, it can be used to store an id or pointer that ties this rigid actor to some other object +// +// Note-1: The passed pointer will be stored in C and as such needs to be pinned, which this function will do. +// You can refer to this for notes on pinning and pointer rules: Refer to: https://pkg.go.dev/cmd/cgo#hdr-Passing_pointers +// +// Note-2: Since this RigidActor object is the one that pinned the user data, it MUST be kept alive at least until ClearUserData is used, at which point the data is unpinned and cleared. +// If this RigidActor object gets garabage collected before clear, the pinner will detect its getting collected with stuff still pinned (which is a leak) and will panic. func (ra *RigidActor) SetUserData(userData unsafe.Pointer) { + + // Note: Do NOT use interfaces here, as we need to ensure the original value + // pointed to is pinned, not the pointer to the interface (i.e. pinning the interface). + // Better avoid crazy to debug issues + + // User data is a Go pointer stored in C/C++ code, and as such MUST be pinned + // before that is done, and must be unpinned when no longer stored in C/C++. + // + // Here we assume every write is of a different object, and so we always unpin before storing + // the new object. + // + // Refer to: https://pkg.go.dev/cmd/cgo#hdr-Passing_pointers + ra.pinner.Unpin() + ra.pinner.Pin(userData) + C.CPxRigidActor_set_userData(ra.cRa, userData) } @@ -677,6 +700,11 @@ func (ra *RigidActor) GetUserData() unsafe.Pointer { return C.CPxRigidActor_get_userData(ra.cRa) } +func (ra *RigidActor) ClearUserData() { + ra.pinner.Unpin() + C.CPxRigidActor_set_userData(ra.cRa, nil) +} + type RigidStatic struct { cRs C.struct_CPxRigidStatic }