From 79549d23bd8e12187614559ef1941176584dfaed Mon Sep 17 00:00:00 2001 From: ksterker Date: Sat, 19 Sep 2009 00:00:21 +0200 Subject: [PATCH] ADDED serializer that can be used to register objects with the save game manager for loading and saving --- src/base/CMakeLists.txt | 1 + src/base/Makefile.am | 1 + src/base/serializer.h | 140 ++++++++++++++++++++++++++++ src/main/adonthell.h | 3 +- src/py-wrappers/adonthell/py_base.i | 41 ++++++++ src/python/callback.h | 12 +-- src/python/python.h | 2 +- test/Makefile.am | 4 +- test/README | 10 +- test/callbacktest.cc | 61 ++++++++++-- test/serializertest.py | 19 ++++ 11 files changed, 272 insertions(+), 22 deletions(-) create mode 100644 src/base/serializer.h create mode 100644 test/serializertest.py diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index e7a8fab..aec2d03 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -32,6 +32,7 @@ set(adonthell_base_HEADERS configuration.h diskwriter_xml.h gettext.h + serializer.h timer.h ) diff --git a/src/base/Makefile.am b/src/base/Makefile.am index 8dc8a48..9ac1435 100644 --- a/src/base/Makefile.am +++ b/src/base/Makefile.am @@ -20,6 +20,7 @@ pkgincludebase_HEADERS = \ hash_map.h \ nls.h \ paths.h \ + serializer.h \ timer.h \ types.h diff --git a/src/base/serializer.h b/src/base/serializer.h new file mode 100644 index 0000000..4b93498 --- /dev/null +++ b/src/base/serializer.h @@ -0,0 +1,140 @@ +/* + Copyright (C) 2009 Kai Sterker + Part of the Adonthell Project http://adonthell.linuxgames.com + + Adonthell is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Adonthell 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Adonthell; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/** + * @file base/serializer.h + * @author Kai Sterker + * + * @brief Gamedata serialization suppert. + * + * + */ + +#ifndef BASE_SAVEGAME_H +#define BASE_SAVEGAME_H + +#include "python/callback_support.h" + +namespace base +{ + /** + * Wrapper for anything that can be serialized to a file on disk. + * It supports static classes, instanciated objects and Python + * classes, as long as those provide the two methods + * + * \li bool load() + * \li bool save(const std::string & path) + * + * Using this wrapper, objects can be registered at the gamedata + * class, which will load or save their contents as appropriate. + */ + class serializer_base + { + public: + /** + * Cleanup. + */ + ~serializer_base () + { + delete Save; + delete Load; + } + + /** + * Serialize object into the given directory. Which filename + * to use is up to the object that is being serialized. + * @param path directory to write into. + * @return true on success, false otherwise. + */ + bool save (const std::string & path) + { + if (!Save) return false; + return (*Save)(path); + } + + /** + * Load object from a file. Which file to load is up to the + * object itself. It must search for that file in Adonthell's + * search path. + * @return true on success, false otherwise. + */ + bool load () + { + if (!Load) return false; + return (*Load)(); + } + +#ifndef SWIG + /// python support + GET_TYPE_NAME(base::serializer_base); +#endif + + protected: + /** + * Prevent instantiation of this abstract base class through + * the user. + */ + serializer_base() + { + Save = NULL; + Load = NULL; + } + + /// callback for saving the wrapped object + base::functor_1ret *Save; + /// callback for loading the wrapped object + base::functor_0ret *Load; + }; + + /** + * Wrapper around static or instanciated class. Called + * py_serializer on Python side (see base.i for specialization). + * This class can be instanciated to register at the savegame + * class. + */ + template + class serializer : public serializer_base + { + public: + /** + * Create a serializer for a static class. + */ + serializer() + { + Save = base::make_functor_ret(&T::save); + Load = base::make_functor_ret(&T::load); + } + + /** + * Create a serializer for the given instance. Note + * that the serializer knows nothing about the instance's + * livetime. So make sure to deregister the serializer + * once the instance gets deleted. + * @param instance object to serialize. + */ + serializer(T *instance) + { + Save = base::make_functor_ret(*instance, &T::save); + Load = base::make_functor_ret(*instance, &T::load); + } + }; + +} + +#endif diff --git a/src/main/adonthell.h b/src/main/adonthell.h index a0f9cbf..3256723 100644 --- a/src/main/adonthell.h +++ b/src/main/adonthell.h @@ -44,8 +44,7 @@ using std::string; namespace adonthell { /** * Subclass adonthell::app to create the entry point for any application - * using the Adonthell framework. The class you create must be named theApp - * in order to be found by the linker. Override the main() method. It will + * using the Adonthell framework. Override the main() method. It will * be called after all operating system dependend intialization has been * completed. You will find the command line args passed to your application * in the member variables argc and argv. diff --git a/src/py-wrappers/adonthell/py_base.i b/src/py-wrappers/adonthell/py_base.i index 8659b0a..c53f68f 100644 --- a/src/py-wrappers/adonthell/py_base.i +++ b/src/py-wrappers/adonthell/py_base.i @@ -9,6 +9,44 @@ #include "base/types.h" #include "base/diskio.h" #include "base/configuration.h" +#include "base/serializer.h" + +#include "python/callback.h" + +// partial specialization of base::serializer so it can be used from Python +namespace base +{ + template<> + serializer::serializer() : serializer_base() + { + } + + template<> + serializer::serializer(PyObject *instance) : serializer_base() + { + PyObject *save = PyObject_GetAttrString (instance, "save"); + if (PyCallable_Check (save)) + { + Save = new python::functor_1ret(save); + } + else + { + fprintf (stderr, "*** serializer: '%s' has no method 'save'!\n", instance->ob_type->tp_name); + } + Py_XDECREF (save); + + PyObject *load = PyObject_GetAttrString (instance, "load"); + if (PyCallable_Check (load)) + { + Load = new python::functor_0ret(load); + } + else + { + fprintf (stderr, "*** serializer: '%s' has no method 'load'!\n", instance->ob_type->tp_name); + } + Py_XDECREF (load); + } +} %} %include "stdint.i" @@ -118,6 +156,9 @@ namespace base { %include "base/diskio.h" %include "base/configuration.h" %include "base/paths.h" +%include "base/serializer.h" + +%template(py_serializer) base::serializer; /* implement friend operators of igzstream */ %extend base::igzstream { diff --git a/src/python/callback.h b/src/python/callback.h index d66b3f6..63f9ed4 100644 --- a/src/python/callback.h +++ b/src/python/callback.h @@ -158,18 +158,18 @@ namespace python void run(P1 arg1, P2 arg2) { PyObject * pyarg1; - PyObject * pyarg2; + PyObject * pyarg2; PyObject * pyres; PyObject * pyargs = PyTuple_New(2); - pyarg1 = pass_instance(arg1); - pyarg2 = pass_instance(arg2); + pyarg1 = pass_instance(arg1); + pyarg2 = pass_instance(arg2); if (!pyarg1) std::cerr << "Warning! Argument not valid!\n" << std::endl; - if (!pyarg2) std::cerr << "Warning! Argument not valid!\n" << std::endl; + if (!pyarg2) std::cerr << "Warning! Argument not valid!\n" << std::endl; // The SetItem steals our reference to pyarg1 PyTuple_SetItem(pyargs, 0, pyarg1); - PyTuple_SetItem(pyargs, 1, pyarg2); + PyTuple_SetItem(pyargs, 1, pyarg2); // We can finally call our function pyres = PyObject_CallObject(callable, pyargs); @@ -205,7 +205,7 @@ namespace python PyObject * pyres; PyObject * pyargs = PyTuple_New(1); - pyarg1 = pass_instance(arg1); + pyarg1 = pass_instance(arg1); if (!pyarg1) std::cerr << "Warning! Argument not valid!\n" << std::endl; // The SetItem steals our reference to pyarg1 diff --git a/src/python/python.h b/src/python/python.h index 465c7fa..7c2a967 100644 --- a/src/python/python.h +++ b/src/python/python.h @@ -195,7 +195,7 @@ namespace python * @return a Python object representing \e arg. */ template <> inline - PyObject * pass_instance(std::string & arg, const ownership own) + PyObject * pass_instance(const std::string & arg, const ownership own) { return PyString_FromString(arg.c_str()); show_traceback(); diff --git a/test/Makefile.am b/test/Makefile.am index 764c09e..23f8cab 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -4,8 +4,8 @@ SUBDIRS = data EXTRA_DIST = README audiotest.py dialogtest.py gfxsave.py itemtest.py \ worldtest.py configtest.py equipmenttest.py gfxtest.py logtest.py \ - convert_data.py eventtest.py guitest.py questtest.py \ - convert_quests.py filereport.py inputtest.py searchtest.py \ + convert_data.py eventtest.py guitest.py questtest.py filereport.py \ + convert_quests.py inputtest.py searchtest.py serializertest.py \ convert_graphics.py CMakeLists.txt README.worldtest smallworld.cc noinst_PROGRAMS = callbacktest diskiotest guitest inputtest gfxtest worldtest \ diff --git a/test/README b/test/README index 14a7a90..df427f0 100644 --- a/test/README +++ b/test/README @@ -19,8 +19,8 @@ Utility Scripts: * convert_graphics.py - Extracts images from v0.3 media files (.anim, .img, .mobj) and - converts them into PNG format. + Extracts images from v0.3 media files (.anim, .img, .mobj, .mchar) + and converts them into PNG format. Run with 'python convert_graphics.py ' * filereport.py @@ -101,8 +101,12 @@ Test Scripts: enter a list of search terms and prints which texts in the library contain these terms. Run with 'python -i searchtest.py' +* serializertest.py + + Tests using the game data serialization for a Python class. Run + with 'python serializertest.py'. + * worldtest.py Tests the world module. Requires the gfx module checked out from CVS. Run with 'python gfxtest.py -g ' - diff --git a/test/callbacktest.cc b/test/callbacktest.cc index fe5c3e8..2440eff 100644 --- a/test/callbacktest.cc +++ b/test/callbacktest.cc @@ -1,5 +1,7 @@ #include #include "base/base.h" +#include "base/serializer.h" + class calltest { @@ -20,16 +22,49 @@ class calltest std::cout << "Method, arg: " << c << " no return\n"; } - void callb2 (char c, char d) - { - std::cout << "Method, args: " << c << " " << d << " no return\n"; - } + void callb2 (char c, char d) + { + std::cout << "Method, args: " << c << " " << d << " no return\n"; + } unsigned int callb1ret(char c) { std::cout << "Method, arg: " << c << " returning " << (unsigned int) c << "\n"; return (unsigned int) c; } + + static void static_callb0() + { + std::cout << "Static method, no arg, no return\n"; + } + + static bool save (const std::string & name) + { + std::cout << "Saving state to " << name << " ...\n"; + return true; + } + + static bool load () + { + std::cout << "Loading state ...\n"; + return true; + } +}; + +class calltest2 +{ +public: + bool save (const std::string & name) + { + std::cout << "Saving state to " << name << " ...\n"; + return true; + } + + bool load () + { + std::cout << "Loading state ...\n"; + return true; + } }; void callb0() @@ -48,13 +83,11 @@ void callb1(char c) std::cout << "Function, arg: " << c << " no return\n"; } - void callb2(char c, char d) { - std::cout << "Function, args: " << c << " " << d << " no return\n"; + std::cout << "Function, args: " << c << " " << d << " no return\n"; } - unsigned int callb1ret(char c) { std::cout << "Function, arg: " << c << " returning " << (unsigned int) c << "\n"; @@ -64,10 +97,14 @@ unsigned int callb1ret(char c) int main(int argc, char * argv[]) { calltest ctest; + calltest2 ctest2; base::functor_0 * f0 = base::make_functor(ctest, &calltest::callb0); (*f0)(); delete f0; + f0 = base::make_functor(&calltest::static_callb0); + (*f0)(); + delete f0; f0 = base::make_functor(&callb0); (*f0)(); delete f0; @@ -100,7 +137,15 @@ int main(int argc, char * argv[]) (*f2)('b','y'); delete f2; - + // --> serializer makes use of callbacks too + base::serializer st; + base::serializer st2(&ctest2); + base::serializer_base *sb = &st; + sb->save("test"); + sb->load(); + sb = &st2; + sb->save("another_test"); + sb->load(); return 0; } diff --git a/test/serializertest.py b/test/serializertest.py new file mode 100644 index 0000000..7fa340e --- /dev/null +++ b/test/serializertest.py @@ -0,0 +1,19 @@ +from adonthell import base, main + +class testserializer(main.AdonthellApp): + + def save (self, name): + print "saving state to", name, "..." + return 1 + + def main (self): + s = base.py_serializer(self) + s.save ("test") + # s.load () + + return 0 + +if __name__ == '__main__': + app = testserializer() + app.init (app.main) +