Revision: 4469
http://playerstage.svn.sourceforge.net/playerstage/?rev=4469&view=rev
Author: robotos
Date: 2008-04-02 05:07:33 -0700 (Wed, 02 Apr 2008)
Log Message:
-----------
Two changes:
Controller plugins: Patch 1909966 by David Olsen
Simplify Gazebo error (gzthrow) usage
Modified Paths:
--------------
code/gazebo/trunk/SConstruct
code/gazebo/trunk/server/GazeboError.hh
code/gazebo/trunk/server/Model.cc
code/gazebo/trunk/server/SConscript
code/gazebo/trunk/server/Simulator.cc
code/gazebo/trunk/server/XMLConfig.cc
code/gazebo/trunk/server/controllers/ControllerFactory.cc
code/gazebo/trunk/server/controllers/ControllerFactory.hh
code/gazebo/trunk/server/controllers/SConscript
code/gazebo/trunk/server/gui/SConscript
code/gazebo/trunk/server/physics/SConscript
code/gazebo/trunk/server/rendering/SConscript
code/gazebo/trunk/server/sensors/SConscript
code/gazebo/trunk/server/sensors/Sensor.cc
Modified: code/gazebo/trunk/SConstruct
===================================================================
--- code/gazebo/trunk/SConstruct 2008-04-01 22:53:29 UTC (rev 4468)
+++ code/gazebo/trunk/SConstruct 2008-04-02 12:07:33 UTC (rev 4469)
@@ -56,6 +56,7 @@
#LIBS=Split('gazebo boost_python')
LIBS=Split('gazebo'),
+ LINKFLAGS=Split('-export-dynamic'),
TARFLAGS = '-c -z',
TARSUFFIX = '.tar.gz',
@@ -134,6 +135,13 @@
if not conf.CheckCHeader('ode/ode.h'):
print " Error: Install ODE (http://www.ode.org)"
Exit(1)
+
+ if not conf.CheckLibWithHeader('ltdl','ltdl.h','CXX'):
+ print " Warning: Failed to find ltdl, no plugin support will be included"
+ env["HAVE_LTDL"]=False
+ else:
+ env["HAVE_LTDL"]=True
+ env["CCFLAGS"].append("-DHAVE_LTDL")
# Check for trimesh support in ODE
if not conf.CheckODELib():
Modified: code/gazebo/trunk/server/GazeboError.hh
===================================================================
--- code/gazebo/trunk/server/GazeboError.hh 2008-04-01 22:53:29 UTC (rev
4468)
+++ code/gazebo/trunk/server/GazeboError.hh 2008-04-02 12:07:33 UTC (rev
4469)
@@ -34,27 +34,32 @@
namespace gazebo
{
-
/// \addtogroup gazebo_server
/// \brief Gazebo error class
/// \{
+ //TODO: global variable, static in the class would be better, if only the
linker didn't oppose to it ...
+ static std::ostringstream throwStream;
/// Throw an error
- #define gzthrow(msg) throw
gazebo::GazeboError(__FILE__,__LINE__,std::string(msg))
+ #define gzthrow(msg) throwStream << "Exception: " << msg << std::endl <<
std::flush;\
+ throw
gazebo::GazeboError(__FILE__,__LINE__,throwStream.str())
+
/// \brief Class to handle errors
///
/**
- Use <tt>gzthrow(std::string)</tt> to throw errors.
+ Use <tt>gzthrow(data1 << data2)</tt> to throw errors.
Example:
\verbatim
+ Recommended new way:
+ gzthrow("This is an error message of type[" << type << "]");
+ Old way:
std::ostringstream stream;
stream << "This is an error message of type[" << type << "]\n";
gzthrow(stream.str());
- or if type is a string, simply:
- gzthrow("This is an error message of type[" + type + "]\n");
+ The final "\n" is not needed anymore, the code should be changed to the new
type.
\endverbatim
*/
@@ -86,7 +91,7 @@
/// \brief Return the error string
/// \return The error string
public: std::string GetErrorStr() const;
-
+
/// \brief The error function
private: std::string file;
Modified: code/gazebo/trunk/server/Model.cc
===================================================================
--- code/gazebo/trunk/server/Model.cc 2008-04-01 22:53:29 UTC (rev 4468)
+++ code/gazebo/trunk/server/Model.cc 2008-04-02 12:07:33 UTC (rev 4469)
@@ -544,6 +544,11 @@
// Get the unique name of the controller
std::string controllerName = node->GetString("name",std::string(),1);
+
+ // See if the controller is in a plugin
+ std::string pluginName = node->GetString("plugin","",0);
+ if (pluginName != "")
+ ControllerFactory::LoadPlugin(pluginName, controllerType);
// Create the controller based on it's type
controller = ControllerFactory::NewController(controllerType, this);
Modified: code/gazebo/trunk/server/SConscript
===================================================================
--- code/gazebo/trunk/server/SConscript 2008-04-01 22:53:29 UTC (rev 4468)
+++ code/gazebo/trunk/server/SConscript 2008-04-02 12:07:33 UTC (rev 4469)
@@ -24,20 +24,23 @@
]
headers.append(
- ['#/server/Global.hh',
- '#/server/Vector3.hh',
+ ['#/server/Entity.hh',
+ '#/server/GazeboError.hh',
+ '#/server/GazeboMessage.hh',
+ '#/server/Global.hh',
+ '#/server/Model.hh',
+ '#/server/Pose3d.hh',
'#/server/Quatern.hh',
- '#/server/Pose3d.hh',
- '#/server/World.hh',
- '#/server/XMLConfig.hh',
+ '#/server/Simulator.hh',
+ '#/server/SingletonT.hh',
+ '#/server/StaticPluginRegister.hh',
+ '#/server/StringValue.hh',
'#/server/Time.hh',
- '#/server/Entity.hh',
- '#/server/GazeboError.hh',
'#/server/UpdateParams.hh',
- '#/server/GazeboMessage.hh',
- '#/server/Model.hh',
- '#/server/Simulator.hh',
- '#/server/StringValue.hh'
+ '#/server/Vector2.hh',
+ '#/server/Vector3.hh',
+ '#/server/World.hh',
+ '#/server/XMLConfig.hh'
] )
staticObjs.append( env.StaticObject(sources) )
Modified: code/gazebo/trunk/server/Simulator.cc
===================================================================
--- code/gazebo/trunk/server/Simulator.cc 2008-04-01 22:53:29 UTC (rev
4468)
+++ code/gazebo/trunk/server/Simulator.cc 2008-04-02 12:07:33 UTC (rev
4469)
@@ -101,17 +101,13 @@
// Load the world file
this->xmlFile=new gazebo::XMLConfig();
-
try
{
xmlFile->Load(worldFileName);
}
catch (GazeboError e)
{
- std::ostringstream stream;
- stream << "The XML config file can not be loaded, please make sure is a
correct file\n"
- << e << "\n";
- gzthrow(stream.str());
+ gzthrow("The XML config file can not be loaded, please make sure is a
correct file\n" << e);
}
XMLConfigNode *rootNode(xmlFile->GetRootNode());
@@ -126,10 +122,7 @@
}
catch (GazeboError e)
{
- std::ostringstream stream;
- stream << "Error loading the GUI\n"
- << e << "\n";
- gzthrow(stream.str());
+ gzthrow( "Error loading the GUI\n" << e);
}
//Initialize RenderingEngine
@@ -139,11 +132,8 @@
}
catch (gazebo::GazeboError e)
{
- std::ostringstream stream;
- stream << "Failed to Initialize the OGRE Rendering system\n"
- << e << "\n";
- gzthrow(stream.str());
- }
+ gzthrow("Failed to Initialize the OGRE Rendering system\n" << e );
+ }
//Preload basic shapes that can be used anywhere
OgreCreator::CreateBasicShapes();
@@ -155,10 +145,7 @@
}
catch (GazeboError e)
{
- std::ostringstream stream;
- stream << "Error loading the GUI\n"
- << e << "\n";
- gzthrow(stream.str());
+ gzthrow("Failed to load the GUI\n" << e);
}
this->loaded=true;
@@ -178,10 +165,8 @@
if (xmlFile->Save(filename)<0)
{
- std::ostringstream stream;
- stream << "The XML file coult not be written back to " << filename <<
std::endl;
- gzthrow(stream.str());
- }
+ gzthrow("The XML file could not be written back to " << filename );
+ }
}
@@ -376,7 +361,7 @@
int x = childNode->GetTupleInt("pos",0,0);
int y = childNode->GetTupleInt("pos",1,0);
std::string type = childNode->GetString("type","fltk",1);
-
+
gzmsg(1) << "Creating GUI:\n\tType[" << type << "] Pos[" << x << " " << y
<< "] Size[" << width << " " << height << "]\n";
if (type != "fltk")
{
@@ -393,7 +378,7 @@
else
{
// Create a dummy GUI
- gzmsg(1) << "Creating a dummy GUI\n";
+ gzmsg(1) <<"Creating a dummy GUI";
this->gui = GuiFactory::NewGui(std::string("dummy"), 0, 0, 0, 0,
std::string());
}
}
Modified: code/gazebo/trunk/server/XMLConfig.cc
===================================================================
--- code/gazebo/trunk/server/XMLConfig.cc 2008-04-01 22:53:29 UTC (rev
4468)
+++ code/gazebo/trunk/server/XMLConfig.cc 2008-04-02 12:07:33 UTC (rev
4469)
@@ -65,7 +65,6 @@
// Load world from file
void XMLConfig::Load( const std::string &filename )
{
- std::ostringstream stream;
this->filename = filename;
// Enable line numbering
@@ -75,8 +74,7 @@
this->xmlDoc = xmlParseFile( this->filename.c_str() );
if (xmlDoc == NULL)
{
- stream << "Unable to parse xml file: " << this->filename;
- gzthrow(stream.str());
+ gzthrow( "Unable to parse xml file: " << this->filename);
}
// Create xpath evaluation context
@@ -97,8 +95,7 @@
this->root = this->CreateNodes( NULL, xmlDocGetRootElement(this->xmlDoc) );
if (this->root == NULL)
{
- stream << "Empty document [" << this->filename << "]";
- gzthrow(stream.str());
+ gzthrow( "Empty document [" << this->filename << "]");
}
}
@@ -111,9 +108,7 @@
this->xmlDoc = xmlParseDoc( (xmlChar*)(str.c_str()) );
if (xmlDoc == NULL)
{
- std::ostringstream stream;
- stream << "unable to parse [" << str << "]";
- gzthrow(stream.str());
+ gzthrow("unable to parse [" << str << "]");
}
// Create wrappers for all the nodes (recursive)
@@ -122,9 +117,7 @@
if (this->root == NULL)
{
- std::ostringstream stream;
- stream << "Empty document [" << str << "\n";
- gzthrow(stream.str());
+ gzthrow( "Empty document [" << str<< "]") ;
}
}
@@ -409,8 +402,7 @@
{
XMLConfigNode *node;
- std::cout << "name = [" << (const char*) this->xmlNode->name
- << "]\n";
+ gzmsg(2) << "name = [" << (const char*) this->xmlNode->name << "]\n";
// Recurse
for (node = this->childFirst; node != NULL; node = node->next)
@@ -482,9 +474,7 @@
if (!value && require)
{
- std::ostringstream stream;
- stream << "unable to find required string attribute[" << key << "] in
world file node[" << this->GetName() << "]";
- gzthrow(stream.str());
+ gzthrow( "unable to find required string attribute[" << key << "] in world
file node[" << this->GetName() << "]");
}
else if ( !value )
return def;
@@ -501,9 +491,7 @@
if (!value && require)
{
- std::ostringstream stream;
- stream << "unable to find required char attribute[" << key << "] in world
file node[" << this->GetName() << "]";
- gzthrow(stream.str());
+ gzthrow("unable to find required char attribute[" << key << "] in world
file node[" << this->GetName() << "]");
}
else if ( !value )
return def;
@@ -553,9 +541,7 @@
if (!value && require)
{
- std::ostringstream stream;
- stream << "unable to find required int attribute[" << key << "] in world
file node[" << this->GetName() << "]";
- gzthrow(stream.str());
+ gzthrow ("unable to find required int attribute[" << key << "] in world
file node[" << this->GetName() << "]");
}
else if ( !value )
return def;
@@ -572,9 +558,7 @@
if (!value && require)
{
- std::ostringstream stream;
- stream << "unable to find required double attribute[" << key << "] in
world file node[" << this->GetName() << "]";
- gzthrow(stream.str());
+ gzthrow( "unable to find required double attribute[" << key << "] in world
file node[" << this->GetName() << "]");
}
else if ( !value )
return def;
@@ -590,9 +574,7 @@
if (!value && require)
{
- std::ostringstream stream;
- stream << "unable to find required float attribute[" << key << "] in world
file node[" << this->GetName() << "]";
- gzthrow(stream.str());
+ gzthrow( "unable to find required float attribute[" << key << "] in world
file node[" << this->GetName() << "]");
}
else if ( !value )
return def;
@@ -611,9 +593,7 @@
if (!value && require)
{
xmlFree(value);
- std::ostringstream stream;
- stream << "unable to find required bool attribute[" << key << "] in world
file node[" << this->GetName() << "]";
- gzthrow(stream.str());
+ gzthrow( "unable to find required bool attribute[" << key << "] in world
file node[" << this->GetName() << "]");
}
else if ( !value )
{
@@ -925,9 +905,7 @@
newNode = xmlNewNode(0, (xmlChar*) key); //I hope we don't need namespaces
here
if (!newNode)
{
- std::ostringstream stream;
- stream << "unable to create an element [" << key << "] in world file
node[" << this->GetName() << "]";
- gzthrow(stream.str());
+ gzthrow( "unable to create an element [" << key << "] in world file
node[" << this->GetName() << "]");
}
xmlNodeSetContent(newNode, (xmlChar*) value);
xmlAddChild(this->xmlNode, newNode);
Modified: code/gazebo/trunk/server/controllers/ControllerFactory.cc
===================================================================
--- code/gazebo/trunk/server/controllers/ControllerFactory.cc 2008-04-01
22:53:29 UTC (rev 4468)
+++ code/gazebo/trunk/server/controllers/ControllerFactory.cc 2008-04-02
12:07:33 UTC (rev 4469)
@@ -30,6 +30,9 @@
#include "gazebo.h"
#include "Controller.hh"
#include "ControllerFactory.hh"
+#ifdef HAVE_LTDL
+#include <ltdl.h>
+#endif // HAVE_LTDL
using namespace gazebo;
@@ -60,3 +63,52 @@
return NULL;
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Load a controller plugin. Used by Model and Sensor when creating
controllers.
+void ControllerFactory::LoadPlugin(const std::string &plugin, const
std::string &classname)
+{
+#ifdef HAVE_LTDL
+
+ static bool init_done = false;
+
+ if (!init_done)
+ {
+ int errors = lt_dlinit();
+ if (errors)
+ {
+ std::ostringstream stream;
+ stream << "Error(s) initializing dynamic loader (" <<
errors << ", " << lt_dlerror() << ")";
+ gzthrow(stream.str());
+ }
+ else
+ init_done = true;
+ }
+
+ lt_dlhandle handle = lt_dlopenext(plugin.c_str());
+
+ if (!handle)
+ {
+ std::ostringstream stream;
+ stream << "Failed to load " << plugin << ": " << lt_dlerror();
+ gzthrow(stream.str());
+ }
+
+ std::string registerName = "RegisterPluginController";
+ void *(*registerFunc)() = (void *(*)())lt_dlsym(handle,
registerName.c_str());
+ if(!registerFunc)
+ {
+ std::ostringstream stream;
+ stream << "Failed to resolve " << registerName << ": " << lt_dlerror();
+ gzthrow(stream.str());
+ }
+
+ // Register the new controller.
+ registerFunc();
+
+#else // HAVE_LTDL
+
+ gzthrow("Cannot load plugins as libtool is not installed.");
+
+#endif // HAVE_LTDL
+}
Modified: code/gazebo/trunk/server/controllers/ControllerFactory.hh
===================================================================
--- code/gazebo/trunk/server/controllers/ControllerFactory.hh 2008-04-01
22:53:29 UTC (rev 4468)
+++ code/gazebo/trunk/server/controllers/ControllerFactory.hh 2008-04-02
12:07:33 UTC (rev 4469)
@@ -58,6 +58,9 @@
/// \brief Create a new instance of a controller. Used by the world when
/// reading the world file.
public: static Controller *NewController(const std::string &classname,
Entity *parent);
+
+ /// \brief Load a controller plugin. Used by Model and Sensor when creating
controllers.
+ public: static void LoadPlugin(const std::string &plugin, const std::string
&classname);
// A list of registered controller classes
private: static std::map<std::string, ControllerFactoryFn> controllers;
@@ -81,6 +84,22 @@
}\
StaticPluginRegister Registered##classname (Register##classname);
+/// \brief Dynamic controller registration macro
+///
+/// Use this macro to register plugin controllers with the server.
+/// \param name Controller type name, as it appears in the world file.
+/// \param classname C++ class name for the controller.
+#define GZ_REGISTER_DYNAMIC_CONTROLLER(name, classname) \
+Controller *New##classname(Entity *entity) \
+{ \
+ return new classname(entity); \
+} \
+extern "C" void RegisterPluginController(); \
+void RegisterPluginController() \
+{\
+ ControllerFactory::RegisterController("dynamic", name, New##classname);\
+}
+
/// \}
}
Modified: code/gazebo/trunk/server/controllers/SConscript
===================================================================
--- code/gazebo/trunk/server/controllers/SConscript 2008-04-01 22:53:29 UTC
(rev 4468)
+++ code/gazebo/trunk/server/controllers/SConscript 2008-04-02 12:07:33 UTC
(rev 4469)
@@ -1,5 +1,5 @@
#Import variable
-Import('env staticObjs sharedObjs')
+Import('env staticObjs sharedObjs headers')
dirs = Split('position2d laser camera factory gripper actarray ptz')
@@ -11,5 +11,11 @@
sources = Split('Controller.cc ControllerFactory.cc')
+headers.append(
+ ['#/server/controllers/ControllerFactory.hh',
+ '#/server/controllers/ControllerStub.hh',
+ '#/server/controllers/Controller.hh'
+ ] )
+
staticObjs.append(env.StaticObject(sources))
sharedObjs.append(env.SharedObject(sources))
Modified: code/gazebo/trunk/server/gui/SConscript
===================================================================
--- code/gazebo/trunk/server/gui/SConscript 2008-04-01 22:53:29 UTC (rev
4468)
+++ code/gazebo/trunk/server/gui/SConscript 2008-04-02 12:07:33 UTC (rev
4469)
@@ -1,5 +1,5 @@
#Import variable
-Import('env staticObjs sharedObjs')
+Import('env staticObjs sharedObjs headers')
#sources = Split('Gui.cc GuiFactory.cc GLWindow.cc MainMenu.cc Toolbar.cc
StatusBar.cc')
@@ -8,7 +8,16 @@
#if conf.CheckCHeader('GL/glx.h'):
sources = Split('Gui.cc DummyGui.cc GuiFactory.cc GLWindow.cc MainMenu.cc
Toolbar.cc StatusBar.cc')
#env = conf.Finish()
-
+headers.append(
+ ['server/gui/DummyGui.hh',
+ 'server/gui/GLWindow.hh',
+ 'server/gui/GuiFactory.hh',
+ 'server/gui/Gui.hh',
+ 'server/gui/MainMenu.hh',
+ 'server/gui/StatusBar.hh',
+ 'server/gui/Toolbar.hh'
+ ] )
+
staticObjs.append(env.StaticObject(sources))
sharedObjs.append(env.SharedObject(sources))
Modified: code/gazebo/trunk/server/physics/SConscript
===================================================================
--- code/gazebo/trunk/server/physics/SConscript 2008-04-01 22:53:29 UTC (rev
4468)
+++ code/gazebo/trunk/server/physics/SConscript 2008-04-02 12:07:33 UTC (rev
4469)
@@ -1,5 +1,5 @@
#Import variable
-Import('env staticObjs sharedObjs')
+Import('env staticObjs sharedObjs headers')
dirs = Split('ode')
@@ -8,5 +8,25 @@
sources = Split('BallJoint.cc Body.cc BoxGeom.cc ContactParams.cc
CylinderGeom.cc Geom.cc Hinge2Joint.cc HingeJoint.cc Joint.cc PhysicsEngine.cc
PlaneGeom.cc SliderJoint.cc SphereGeom.cc UniversalJoint.cc RayGeom.cc
TrimeshGeom.cc HeightmapGeom.cc')
+headers.append(
+ ['server/physics/BallJoint.hh',
+ 'server/physics/Body.hh',
+ 'server/physics/BoxGeom.hh',
+ 'server/physics/ContactParams.hh',
+ 'server/physics/CylinderGeom.hh',
+ 'server/physics/Geom.hh',
+ 'server/physics/HeightmapGeom.hh',
+ 'server/physics/Hinge2Joint.hh',
+ 'server/physics/HingeJoint.hh',
+ 'server/physics/Joint.hh',
+ 'server/physics/PhysicsEngine.hh',
+ 'server/physics/PlaneGeom.hh',
+ 'server/physics/RayGeom.hh',
+ 'server/physics/SliderJoint.hh',
+ 'server/physics/SphereGeom.hh',
+ 'server/physics/TrimeshGeom.hh',
+ 'server/physics/UniversalJoint.hh'
+ ] )
+
staticObjs.append( env.StaticObject(sources) )
sharedObjs.append( env.SharedObject(sources) )
Modified: code/gazebo/trunk/server/rendering/SConscript
===================================================================
--- code/gazebo/trunk/server/rendering/SConscript 2008-04-01 22:53:29 UTC
(rev 4468)
+++ code/gazebo/trunk/server/rendering/SConscript 2008-04-02 12:07:33 UTC
(rev 4469)
@@ -1,7 +1,19 @@
#Import variable
-Import('env staticObjs sharedObjs')
+Import('env staticObjs sharedObjs headers')
sources = Split('OgreCreator.cc OgreAdaptor.cc OgreFrameListener.cc
OgreDynamicRenderable.cc OgreDynamicLines.cc OgreSimpleShape.cc OgreHUD.cc
MovableText.cc OgreVisual.cc')
+headers.append(
+ ['#/server/rendering/MovableText.hh',
+ '#/server/rendering/OgreAdaptor.hh',
+ '#/server/rendering/OgreCreator.hh',
+ '#/server/rendering/OgreDynamicLines.hh',
+ '#/server/rendering/OgreDynamicRenderable.hh',
+ '#/server/rendering/OgreFrameListener.hh',
+ '#/server/rendering/OgreHUD.hh',
+ '#/server/rendering/OgreSimpleShape.hh',
+ '#/server/rendering/OgreVisual.hh'
+ ] )
+
staticObjs.append( env.StaticObject(sources) )
sharedObjs.append( env.SharedObject(sources) )
Modified: code/gazebo/trunk/server/sensors/SConscript
===================================================================
--- code/gazebo/trunk/server/sensors/SConscript 2008-04-01 22:53:29 UTC (rev
4468)
+++ code/gazebo/trunk/server/sensors/SConscript 2008-04-02 12:07:33 UTC (rev
4469)
@@ -1,5 +1,5 @@
#Import variable
-Import('env staticObjs sharedObjs')
+Import('env staticObjs sharedObjs headers')
dirs = Split('camera ray')
@@ -8,5 +8,11 @@
sources = Split('Sensor.cc SensorFactory.cc')
+headers.append(
+ ['server/sensors/SensorFactory.hh',
+ 'server/sensors/SensorStub.hh',
+ 'server/sensors/Sensor.hh'
+ ] )
+
staticObjs.append( env.StaticObject(sources) )
sharedObjs.append( env.SharedObject(sources) )
Modified: code/gazebo/trunk/server/sensors/Sensor.cc
===================================================================
--- code/gazebo/trunk/server/sensors/Sensor.cc 2008-04-01 22:53:29 UTC (rev
4468)
+++ code/gazebo/trunk/server/sensors/Sensor.cc 2008-04-02 12:07:33 UTC (rev
4469)
@@ -127,6 +127,11 @@
stream << "No interface defined for " << controllerName << "controller";
gzthrow(stream.str());
}*/
+
+ // See if the controller is in a plugin
+ std::string pluginName = node->GetString("plugin","",0);
+ if (pluginName != "")
+ ControllerFactory::LoadPlugin(pluginName, controllerType);
// Create the controller based on it's type
this->controller = ControllerFactory::NewController(controllerType, this);
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://ad.doubleclick.net/clk;164216239;13503038;w?http://sf.net/marketplace
_______________________________________________
Playerstage-commit mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/playerstage-commit