Dynamic properties revealed

Published June 09, 2010
Advertisement
Dynamic properties revealed
Properties continued from here.
Alright, as I promised in the last entry here is the full source for the property container I'm using. The core functionality of the prop::container is to map strings to values of any type. This is done using the templated get/set functions.

The set() function either sets an existing property to a value or creates a new one. The property type is specified via the template parameter.

The get() function requires that the sought property of the requested type exists or it will throw an exception. The type must match the one used with set().

To avoid having to wrap each get() call in a try-catch there is a req() function that should be called at the beginning of each scope that uses the property. If the given property exists the req() call does nothing. If it doesn't, req() creates the property and sets it to some initial value. One can think of req() as the equivalent of a variable declaration.

Right now there are no functions to remove or query existence of properties but they should be simple to write.

There is finally a set_callback() function that takes a existing property name and a prop::weak_callback_ptr object. This sets a callback object that should be invoked every time the given property is written to. To use this you should inherit the prop::callback class and implement the operator(). Then you should create an instance of that object somewhere using prop::callback_ptr p(new derived_cb()); This will give you a reference counted pointer object that can safely be given to the property container by set_callback("prop_name", prop::weak_callback_ptr(p));

Since callback_ptr is reference counted it will not need to be manually deleted. The pointer we give to set_callback() will not become orphaned if our callback object is destroyed. Because we send in a weak pointer the callback object will not be pinned down by the container. If the callback object is destroyed the weak pointer will become invalidated and the container safely removes it.

Beyond the addition of the has() and rem() functions noted above there are a few things I'd like to add in the future. Most importantly a property should be able to have more than one callback associated so that systems can listen independently. Currently req() does not enforce that the required property has the right type which may be a design error. Also I'd like the prop::prop_value and prop::property classes to be private to prop::container to clean up the external interface.

The code is contained in one C++ header file and it needs several boost classes to build. If you don't have boost, get it here. RTTI needs to be enabled when compiling. This is a requirement of boost::any.

? Check out the full implementation below, after the usage example program.

Use it, abuse it, and if you find anything good or bad about it, tell me! [smile]

And, yeah, funny story. GD does not allow me to upload .HPP header files. I assume they have been shown to be potentially more harmful than their .H brethren. [looksaround] There is a zip-archive containing both files though.

Sample program [source]
#include #include "prop_container.hpp"int main(int argc, char* argv[]){	prop::container props;	// Require the new property and output its value.	props.req<int>("my_int", 0);	std::cout << "get(\"my_int\") gives: " << props.get<int>("my_int") << std::endl;	// Define a local var to be the copy_callback destination.	int copied_result = 110;	// Create a copy_callback object and register it with the container.	prop::callback_ptr p(new prop::copy_callback<int>(copied_result));	props.set_callback("my_int", prop::weak_callback_ptr(p));	std::cout << "Initially copied_result contains: " << copied_result << std::endl;	// Now write to the property and output the results of the write.	props.set<int>("my_int", 120);	std::cout << "After writing copied_result contains: " << copied_result << std::endl;	std::cout << "get(\"my_int\") gives: " << props.get<int>("my_int") << std::endl;	// No cleanup necessary. All objects are local or ref-counted. Happy times!	return 0;}/*Program output:get("my_int") gives: 0Initially copied_result contains: 110After writing copied_result contains: 120get("my_int") gives: 120Press any key to continue . . .*/


Property container code [source]
// Copyright 2009-2010 Staffan Einarsson.// This file is part of the Citizen project.#ifndef PROP_CONTAINER_H#define PROP_CONTAINER_H#include #include #include #include namespace prop {	// This is the value type wrapper class that is contained in the container.	// It has templated get/set functions that do type-safe conversions.	class prop_value {	public:		// Contructors and assignment operators.		prop_value() {}		prop_value(const prop_value &o) : val(o.val) {}		template prop_value(const T &o) : val(o) {}		prop_value &operator=(const prop_value &o) { val = o.val; return *this; }		template prop_value &operator=(const T &o) { val = o; return *this; }		// Get/set pair		template		T get( ) {			return boost::any_cast( val );		}		template		void set( T new_val ) {			val = new_val;		}		private:		// Property value		boost::any val;	};	// --------------------------------------------------------------	// This is the base class for all callbacks used in the container.	// Derive from this to make your own callback.	class callback {	public:		// When the callback is invoked on a property write, the old value and		// new value are sent to this function.		virtual void operator()(prop_value old_val, prop_value new_val) = 0;	};	// Smart pointer types used by the container.	typedef boost::shared_ptr callback_ptr;	typedef boost::weak_ptr weak_callback_ptr;	// Sample callback implementation that simply copies the new val to	// a specifed variable. The class gets a reference to the destination	// variable through its constructor.	template	class copy_callback : public callback {	public:		copy_callback(T &val) : val_ref(val) {}		void operator()(prop_value old_val, prop_value new_val) {			val_ref = new_val.get();		}	private:		T &val_ref;	};	// --------------------------------------------------------------	// This is the container item class, which has a value and an associated	// callback pointer. The operations include typed get/set value and set/unset	// callback.	class property {	public:		// Constructors and assignment operators.		property() {}		property(const property &o) : val(o.val), callback(o.callback) {}		template property(const T &o) : val(o) {}		property &operator=(const property &o) { val = o.val; callback = o.callback; return *this; }		template property &operator=(const T &o) { val = o; callback.clear(); return *this; }		// Get/set pair		template		T get() {			return val.get();		}		template		void set(T new_val) {			// Set new value. Send old and new val to callback.			prop_value old_val = val;			val = new_val;			// Check that callback ptr is still valid before calling.			if (!callback.expired()) (*callback.lock())(old_val, val);		}		// Set/unset callback pair		void set_callback(const weak_callback_ptr &new_callback) {			callback = new_callback;		}		void unset_callback() {			callback.reset();		}	private:		prop_value val;		// A pointer to the callback function object.		// Weak so it doesn't prvent the callback from deleting.		weak_callback_ptr callback;	};	// --------------------------------------------------------------	// This is the top level container class, the one to use in external code.	// It has operations to manipulate values and callbacks on properties identified	// by their names.	class container {	public:		// Only get the value if the property exists and is of the right type.		template		T get( const std::string &name ) {			property_map::iterator it = props.find(name);			if (it != props.end()) {				return it->second.get();			}			throw does_not_exist("Property \"" + name + "\" does not exist.");		}		// Set value of an existing property or create a new one.		template		void set(const std::string &name, T val) {			// Create a new value or overwrite the old one.			property_map::iterator it = props.find(name);			if (it == props.end()) {				props[name] = property(val);			} else {				it->second.set(val);			}		}		// Set new callback only if given property exists.		void set_callback(const std::string &name, const weak_callback_ptr &callback) {			property_map::iterator it = props.find(name);			if (it != props.end()) {				it->second.set_callback(callback);				return;			}			throw does_not_exist("Property \"" + name + "\" does not exist.");		}		// Unset callback only if given property exists.		void unset_callback(const std::string &name) {			// Unset callback only if property is found.			property_map::iterator it = props.find(name);			if (it != props.end()) {				it->second.unset_callback();				return;			}			throw does_not_exist("Property \"" + name + "\" does not exist.");		}		// Require that the given property exists.		// If not, create a new one with given type and value.		template		void req(const std::string &name, T initVal) {			// Same as set() but never overwrites.			property_map::iterator it = props.find(name);			if (it == props.end()) {				props[name] = property(initVal);			}		}		// Exception types used by the container.		typedef boost::bad_any_cast bad_cast;		typedef std::out_of_range does_not_exist;	private:		// Map containing the properties.		typedef std::map property_map;		property_map props;	};}#endif
Previous Entry More Box2D integration
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement