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:
Thomas Geymayer
2014-11-23 23:39:56 +01:00
parent 958ae9bdf0
commit 9537876bba
10 changed files with 216 additions and 59 deletions

View File

@@ -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}
)

View 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_ */

View File

@@ -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>

View File

@@ -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>

View 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());
}

View File

@@ -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&) )

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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