Nasal: add an naRef to ghosts to allow for proper gc of dependent objects/ghosts.
This allows for binding the lifetime of any nasal object to the lifetime of a ghost. Otherwise circular references from objects saved within the ghost would prevent the ghost from being garbage collected.
This commit is contained in:
@@ -35,16 +35,21 @@ simgear_component(nasal/cppbind nasal/cppbind "${SOURCES}" "${HEADERS}")
|
||||
simgear_component(nasal/cppbind/detail nasal/cppbind/detail "" "${DETAIL_HEADERS}")
|
||||
|
||||
add_boost_test(cppbind_ghost
|
||||
SOURCES cppbind_test_ghost.cxx
|
||||
SOURCES test/cppbind_test_ghost.cxx
|
||||
LIBRARIES ${TEST_LIBS}
|
||||
)
|
||||
|
||||
add_boost_test(cppbind_misc
|
||||
SOURCES cppbind_test.cxx
|
||||
SOURCES test/cppbind_test.cxx
|
||||
LIBRARIES ${TEST_LIBS}
|
||||
)
|
||||
|
||||
add_boost_test(nasal_gc_test
|
||||
SOURCES test/nasal_gc_test.cxx
|
||||
LIBRARIES ${TEST_LIBS}
|
||||
)
|
||||
|
||||
add_boost_test(nasal_num
|
||||
SOURCES nasal_num_test.cxx
|
||||
SOURCES test/nasal_num_test.cxx
|
||||
LIBRARIES ${TEST_LIBS}
|
||||
)
|
||||
76
simgear/nasal/cppbind/test/TestContext.hxx
Normal file
76
simgear/nasal/cppbind/test/TestContext.hxx
Normal file
@@ -0,0 +1,76 @@
|
||||
///@file
|
||||
/// Nasal context for testing and executing code
|
||||
///
|
||||
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#ifndef SG_NASAL_TESTCONTEXT_HXX_
|
||||
#define SG_NASAL_TESTCONTEXT_HXX_
|
||||
|
||||
#include <simgear/nasal/cppbind/NasalCallContext.hxx>
|
||||
|
||||
class TestContext:
|
||||
public nasal::CallContext
|
||||
{
|
||||
public:
|
||||
TestContext():
|
||||
CallContext(naNewContext(), naNil(), 0, 0)
|
||||
{}
|
||||
|
||||
~TestContext()
|
||||
{
|
||||
naFreeContext(c);
|
||||
}
|
||||
|
||||
void runGC()
|
||||
{
|
||||
naFreeContext(c);
|
||||
naGC();
|
||||
c = naNewContext();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T from_str(const std::string& str)
|
||||
{
|
||||
return from_nasal<T>(to_nasal(str));
|
||||
}
|
||||
|
||||
naRef exec(const std::string& code_str, nasal::Me me)
|
||||
{
|
||||
int err_line = -1;
|
||||
naRef code = naParseCode( c, to_nasal("<TextContext::exec>"), 0,
|
||||
(char*)code_str.c_str(), code_str.length(),
|
||||
&err_line );
|
||||
if( !naIsCode(code) )
|
||||
throw std::runtime_error("Failed to parse code: " + code_str);
|
||||
|
||||
return naCallMethod(code, me, 0, 0, naNil());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T exec(const std::string& code)
|
||||
{
|
||||
return from_nasal<T>(exec(code, naNil()));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T convert(const std::string& str)
|
||||
{
|
||||
return from_nasal<T>(to_nasal(str));
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* SG_NASAL_TESTCONTEXT_HXX_ */
|
||||
@@ -1,10 +1,9 @@
|
||||
#define BOOST_TEST_MODULE cppbind
|
||||
#include <BoostTestTargetConfig.h>
|
||||
|
||||
#include "Ghost.hxx"
|
||||
#include "NasalHash.hxx"
|
||||
#include "NasalString.hxx"
|
||||
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalHash.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalString.hxx>
|
||||
#include <simgear/math/SGMath.hxx>
|
||||
#include <simgear/structure/map.hxx>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#define BOOST_TEST_MODULE cppbind
|
||||
#include <BoostTestTargetConfig.h>
|
||||
|
||||
#include "Ghost.hxx"
|
||||
#include "NasalContext.hxx"
|
||||
#include <simgear/nasal/cppbind/Ghost.hxx>
|
||||
#include <simgear/nasal/cppbind/NasalContext.hxx>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/weak_ptr.hpp>
|
||||
93
simgear/nasal/cppbind/test/nasal_gc_test.cxx
Normal file
93
simgear/nasal/cppbind/test/nasal_gc_test.cxx
Normal file
@@ -0,0 +1,93 @@
|
||||
#define BOOST_TEST_MODULE nasal
|
||||
#include <BoostTestTargetConfig.h>
|
||||
|
||||
#include "TestContext.hxx"
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
|
||||
static std::set<intptr_t> active_instances;
|
||||
|
||||
static void ghost_destroy(void* p)
|
||||
{
|
||||
active_instances.erase((intptr_t)p);
|
||||
}
|
||||
|
||||
static naGhostType ghost_type = {
|
||||
&ghost_destroy,
|
||||
"TestGhost",
|
||||
0, // get_member
|
||||
0 // set_member
|
||||
};
|
||||
|
||||
static naRef createTestGhost(TestContext& c, intptr_t p)
|
||||
{
|
||||
active_instances.insert(p);
|
||||
return naNewGhost(c.c, &ghost_type, (void*)p);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
BOOST_AUTO_TEST_CASE( ghost_gc )
|
||||
{
|
||||
TestContext c;
|
||||
BOOST_REQUIRE(active_instances.empty());
|
||||
|
||||
//-----------------------------------------------
|
||||
// Just create ghosts and let the gc destroy them
|
||||
|
||||
naRef g1 = createTestGhost(c, 1),
|
||||
g2 = createTestGhost(c, 2);
|
||||
|
||||
BOOST_CHECK_EQUAL(active_instances.count(1), 1);
|
||||
BOOST_CHECK_EQUAL(active_instances.count(2), 1);
|
||||
BOOST_CHECK_EQUAL(active_instances.size(), 2);
|
||||
|
||||
c.runGC();
|
||||
|
||||
BOOST_REQUIRE(active_instances.empty());
|
||||
|
||||
|
||||
//-----------------------------------------------
|
||||
// Create some more ghosts and save one instance
|
||||
// from being garbage collected.
|
||||
|
||||
g1 = createTestGhost(c, 1);
|
||||
g2 = createTestGhost(c, 2);
|
||||
|
||||
int gc1 = naGCSave(g1);
|
||||
c.runGC();
|
||||
|
||||
BOOST_CHECK_EQUAL(active_instances.count(1), 1);
|
||||
BOOST_CHECK_EQUAL(active_instances.size(), 1);
|
||||
|
||||
naGCRelease(gc1);
|
||||
c.runGC();
|
||||
|
||||
BOOST_REQUIRE(active_instances.empty());
|
||||
|
||||
|
||||
//-----------------------------------------------
|
||||
// Now test attaching one ghost to another
|
||||
|
||||
g1 = createTestGhost(c, 1);
|
||||
g2 = createTestGhost(c, 2);
|
||||
|
||||
gc1 = naGCSave(g1);
|
||||
naGhost_setData(g1, g2); // bind lifetime of g2 to g1...
|
||||
|
||||
c.runGC();
|
||||
|
||||
BOOST_CHECK_EQUAL(active_instances.count(1), 1);
|
||||
BOOST_CHECK_EQUAL(active_instances.count(2), 1);
|
||||
BOOST_CHECK_EQUAL(active_instances.size(), 2);
|
||||
|
||||
naGhost_setData(g1, naNil()); // cut connection
|
||||
c.runGC();
|
||||
|
||||
BOOST_CHECK_EQUAL(active_instances.count(1), 1);
|
||||
BOOST_CHECK_EQUAL(active_instances.size(), 1);
|
||||
|
||||
naGCRelease(gc1);
|
||||
c.runGC();
|
||||
|
||||
BOOST_REQUIRE(active_instances.empty());
|
||||
}
|
||||
@@ -1,51 +1,7 @@
|
||||
#define BOOST_TEST_MODULE cppbind
|
||||
#define BOOST_TEST_MODULE nasal
|
||||
#include <BoostTestTargetConfig.h>
|
||||
|
||||
#include "NasalCallContext.hxx"
|
||||
|
||||
class TestContext:
|
||||
public nasal::CallContext
|
||||
{
|
||||
public:
|
||||
TestContext():
|
||||
CallContext(naNewContext(), naNil(), 0, 0)
|
||||
{}
|
||||
|
||||
~TestContext()
|
||||
{
|
||||
naFreeContext(c);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T from_str(const std::string& str)
|
||||
{
|
||||
return from_nasal<T>(to_nasal(str));
|
||||
}
|
||||
|
||||
naRef exec(const std::string& code_str, nasal::Me me)
|
||||
{
|
||||
int err_line = -1;
|
||||
naRef code = naParseCode( c, to_nasal("<TextContext::exec>"), 0,
|
||||
(char*)code_str.c_str(), code_str.length(),
|
||||
&err_line );
|
||||
if( !naIsCode(code) )
|
||||
throw std::runtime_error("Failed to parse code: " + code_str);
|
||||
|
||||
return naCallMethod(code, me, 0, 0, naNil());
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T exec(const std::string& code)
|
||||
{
|
||||
return from_nasal<T>(exec(code, naNil()));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T convert(const std::string& str)
|
||||
{
|
||||
return from_nasal<T>(to_nasal(str));
|
||||
}
|
||||
};
|
||||
#include "TestContext.hxx"
|
||||
|
||||
static void runNumTests( double (TestContext::*test_double)(const std::string&),
|
||||
int (TestContext::*test_int)(const std::string&) )
|
||||
@@ -175,6 +175,7 @@ struct naGhost {
|
||||
GC_HEADER;
|
||||
naGhostType* gtype;
|
||||
void* ptr;
|
||||
naRef data; //!< Nasal data bound to the lifetime of the ghost.
|
||||
};
|
||||
|
||||
struct naPool {
|
||||
|
||||
@@ -177,7 +177,7 @@ static void newBlock(struct naPool* p, int need)
|
||||
newb->next = p->blocks;
|
||||
p->blocks = newb;
|
||||
naBZero(newb->block, need * p->elemsz);
|
||||
|
||||
|
||||
if(need > p->freesz - p->freetop) need = p->freesz - p->freetop;
|
||||
p->nfree = 0;
|
||||
p->free = p->free0 + p->freetop;
|
||||
@@ -263,6 +263,9 @@ static void mark(naRef r)
|
||||
mark(PTR(r).func->namespace);
|
||||
mark(PTR(r).func->next);
|
||||
break;
|
||||
case T_GHOST:
|
||||
mark(PTR(r).ghost->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +303,7 @@ static void reap(struct naPool* p)
|
||||
|
||||
// allocs of this type until the next collection
|
||||
globals->allocCount += total/2;
|
||||
|
||||
|
||||
// Allocate more if necessary (try to keep 25-50% of the objects
|
||||
// available)
|
||||
if(p->nfree < total/4) {
|
||||
|
||||
@@ -143,10 +143,11 @@ naRef naNewGhost(naContext c, naGhostType* type, void* ptr)
|
||||
// ensure 'simple' ghost users don't see garbage for these fields
|
||||
type->get_member = 0;
|
||||
type->set_member = 0;
|
||||
|
||||
|
||||
ghost = naNew(c, T_GHOST);
|
||||
PTR(ghost).ghost->gtype = type;
|
||||
PTR(ghost).ghost->ptr = ptr;
|
||||
PTR(ghost).ghost->data = naNil();
|
||||
return ghost;
|
||||
}
|
||||
|
||||
@@ -155,6 +156,7 @@ naRef naNewGhost2(naContext c, naGhostType* t, void* ptr)
|
||||
naRef ghost = naNew(c, T_GHOST);
|
||||
PTR(ghost).ghost->gtype = t;
|
||||
PTR(ghost).ghost->ptr = ptr;
|
||||
PTR(ghost).ghost->data = naNil();
|
||||
return ghost;
|
||||
}
|
||||
|
||||
@@ -170,9 +172,21 @@ void* naGhost_ptr(naRef ghost)
|
||||
return PTR(ghost).ghost->ptr;
|
||||
}
|
||||
|
||||
void naGhost_setData(naRef ghost, naRef data)
|
||||
{
|
||||
if(IS_GHOST(ghost))
|
||||
PTR(ghost).ghost->data = data;
|
||||
}
|
||||
|
||||
naRef naGhost_data(naRef ghost)
|
||||
{
|
||||
if(!IS_GHOST(ghost)) return naNil();
|
||||
return PTR(ghost).ghost->data;
|
||||
}
|
||||
|
||||
naRef naNil()
|
||||
{
|
||||
naRef r;
|
||||
naRef r;
|
||||
SETPTR(r, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -281,6 +281,16 @@ naRef naNewGhost(naContext c, naGhostType* t, void* ghost);
|
||||
naRef naNewGhost2(naContext c, naGhostType* t, void* ghost);
|
||||
naGhostType* naGhost_type(naRef ghost);
|
||||
void* naGhost_ptr(naRef ghost);
|
||||
/**
|
||||
* Attach a nasal object to the given ghost. Binds the lifetime of @a data to
|
||||
* the lifetime of the @a ghost.
|
||||
*/
|
||||
void naGhost_setData(naRef ghost, naRef data);
|
||||
/**
|
||||
* Retrieve the object attached to the @a ghost, previously set with
|
||||
* naGhost_setData().
|
||||
*/
|
||||
naRef naGhost_data(naRef ghost);
|
||||
int naIsGhost(naRef r);
|
||||
|
||||
// Acquires a "modification lock" on a context, allowing the C code to
|
||||
|
||||
Reference in New Issue
Block a user