From 17bb62e4416578a001366ac966effd6330498015 Mon Sep 17 00:00:00 2001 From: Paul Merrill Date: Tue, 17 Sep 2013 04:34:35 -0700 Subject: [PATCH] finish move to Python-handled imports Builds on Mac. Makefile is broken on Linux. Things currently broken: * creating Script object from PyObject (ergo, AI is broken) * pausing music from script (not new, this happened with the move to Music singleton) --- data/base/__init__.py | 2 - data/base/baseapi/__init__.py | 2 - data/base/init.py | 3 - data/testing/areas/cave01.py | 65 ++--- data/testing/areas/cave01.tmx | 6 +- data/testing/areas/grove01.py | 68 ++--- data/testing/areas/grove01.tmx | 6 +- data/testing/areas/grove02.py | 56 ++-- data/testing/areas/grove02.tmx | 2 +- data/testing/areas/grove_house.py | 16 +- data/testing/areas/grove_house.tmx | 8 +- data/testing/areas/secret_room.tmx | 6 +- data/testing/areas/sounds.py | 4 + data/testing/init_area.py | 2 + data/testing/init_world.py | 19 +- data/testing/world.conf | 4 +- src/area-tmx.cpp | 57 ++-- src/area.cpp | 7 +- src/area.h | 4 +- src/backend-gosu/gosu-cbuffer.h | 4 +- src/backend-gosu/gosu-image.cpp | 2 +- src/backend-gosu/gosu-image.h | 6 +- src/bytecode.cpp | 119 --------- src/bytecode.h | 67 ----- src/entity.cpp | 19 +- src/entity.h | 7 +- src/formatter.cpp | 54 +++- src/formatter.h | 6 +- src/{tiledimage-impl.cpp => gosu-tiledimage.cpp} | 6 +- src/{tiledimage-impl.h => gosu-tiledimage.h} | 6 +- src/image.cpp | 1 + src/log.cpp | 4 +- src/music.cpp | 2 +- src/{scriptinst.h => python-bindings-template.cpp} | 67 +++-- src/python-bindings.cpp | 4 +- src/{pyworldfinder.cpp => python-importer.cpp} | 64 ++--- src/{pyworldfinder.h => python-importer.h} | 10 +- src/python.cpp | 98 ++++--- src/python.h | 57 +--- src/random.cpp | 1 + src/reader.cpp | 90 ++----- src/reader.h | 6 - src/script-python.cpp | 293 +++++++++++++++++++++ src/{tiledimage-impl.h => script-python.h} | 26 +- src/{image.cpp => script.cpp} | 13 +- src/{tiledimage-impl.h => script.h} | 28 +- src/scriptinst.cpp | 205 -------------- src/sound.cpp | 1 + src/tile.cpp | 24 +- src/tile.h | 12 +- src/timeout.cpp | 75 +++--- src/timeout.h | 6 +- src/timer.cpp | 10 +- src/world.cpp | 18 +- src/world.h | 12 +- 55 files changed, 844 insertions(+), 916 deletions(-) delete mode 100644 data/base/init.py rewrite data/testing/areas/cave01.py (89%) rewrite data/testing/areas/grove01.py (73%) rewrite data/testing/areas/grove02.py (63%) delete mode 100644 src/bytecode.cpp delete mode 100644 src/bytecode.h rename src/{tiledimage-impl.cpp => gosu-tiledimage.cpp} (96%) copy src/{tiledimage-impl.h => gosu-tiledimage.h} (94%) rename src/{scriptinst.h => python-bindings-template.cpp} (54%) rename src/{pyworldfinder.cpp => python-importer.cpp} (69%) rename src/{pyworldfinder.h => python-importer.h} (92%) create mode 100644 src/script-python.cpp copy src/{tiledimage-impl.h => script-python.h} (77%) copy src/{image.cpp => script.cpp} (90%) rename src/{tiledimage-impl.h => script.h} (75%) delete mode 100644 src/scriptinst.cpp diff --git a/data/base/__init__.py b/data/base/__init__.py index 01e7de8..e69de29 100644 --- a/data/base/__init__.py +++ b/data/base/__init__.py @@ -1,2 +0,0 @@ -import baseapi - diff --git a/data/base/baseapi/__init__.py b/data/base/baseapi/__init__.py index 6c5bfc6..e69de29 100644 --- a/data/base/baseapi/__init__.py +++ b/data/base/baseapi/__init__.py @@ -1,2 +0,0 @@ -import baseapi.random - diff --git a/data/base/init.py b/data/base/init.py deleted file mode 100644 index 6416acc..0000000 --- a/data/base/init.py +++ /dev/null @@ -1,3 +0,0 @@ -import baseapi -import baseapi.init - diff --git a/data/testing/areas/cave01.py b/data/testing/areas/cave01.py dissimilarity index 89% index 66f5c1a..5fedb93 100644 --- a/data/testing/areas/cave01.py +++ b/data/testing/areas/cave01.py @@ -1,30 +1,35 @@ -def cave_load(): - global cave_overlay_alpha - if cave_fall_stage == 0: - Player.frozen = True - Player.phase = "up" - cave_overlay_alpha = 255 - Area.color_overlay(0, 0, 0, cave_overlay_alpha) - -def cave_tick(): - global cave_fall_gfx_timer, cave_fall_snd, cave_fall_stage, cave_overlay_alpha - if cave_fall_stage == 0: - cave_fall_stage = 1 - cave_fall_snd = Sound.play("sounds/rockfall.oga") - - if cave_fall_stage == 1: - if not cave_fall_snd.playing: - cave_fall_stage = 2 - cave_fall_gfx_timer = new_timer() - cave_fall_gfx_timer.running = True - - if cave_fall_stage == 2: - if cave_fall_gfx_timer.count > 0.02: - cave_fall_gfx_timer.reset() - if cave_overlay_alpha > 0: - cave_overlay_alpha -= 5 - Area.color_overlay(0, 0, 0, cave_overlay_alpha) - else: - cave_fall_stage = 3 - Player.frozen = False - +fall_gfx_timer = None +fall_snd = None +fall_stage = 0 +overlay_alpha = 0 + +def load(): + global overlay_alpha + if fall_stage == 0: + Player.frozen = True + Player.phase = "up" + overlay_alpha = 255 + Area.color_overlay(0, 0, 0, overlay_alpha) + +def tick(): + global fall_gfx_timer, fall_snd, fall_stage, overlay_alpha + if fall_stage == 0: + fall_stage = 1 + fall_snd = snd = Sound.play("sounds/rockfall.oga") + + if fall_stage == 1: + if not fall_snd.playing: + fall_stage = 2 + fall_gfx_timer = new_timer() + fall_gfx_timer.running = True + + if fall_stage == 2: + if fall_gfx_timer.count > 0.02: + fall_gfx_timer.reset() + if overlay_alpha > 0: + overlay_alpha -= 5 + Area.color_overlay(0, 0, 0, overlay_alpha) + else: + fall_stage = 3 + Player.frozen = False + diff --git a/data/testing/areas/cave01.tmx b/data/testing/areas/cave01.tmx index c8abe35..380770b 100644 --- a/data/testing/areas/cave01.tmx +++ b/data/testing/areas/cave01.tmx @@ -2,8 +2,8 @@ - - + + @@ -663,7 +663,7 @@ - + diff --git a/data/testing/areas/grove01.py b/data/testing/areas/grove01.py dissimilarity index 73% index 53b5f0e..9040d22 100644 --- a/data/testing/areas/grove01.py +++ b/data/testing/areas/grove01.py @@ -1,30 +1,38 @@ -def grove01_load(): - global grove01_ai_wizard - grove01_ai_wizard = stdlib_ai_wander("entities/wizard/wizard.xml", "down", 6, 3, 0.0, 1.0, 4) - - cloud = Area.new_overlay("entities/cloud/cloud.xml", 11, 2, 10.0, "down") - cloud.move(-400, 0) - -def grove01_tick(): - global grove01_drinking, grove01_duration, grove01_max_alpha - if grove01_drinking == True: - progress = grove01_well_timer.count / grove01_duration - if progress < 0.5: - overlay_alpha = int(2 * grove01_max_alpha * progress) - elif progress < 1.0: - overlay_alpha = int(2 * grove01_max_alpha * (1 - progress)) - else: - overlay_alpha = 0 - grove01_drinking = False - Area.color_overlay(255, 255, 255, overlay_alpha) # white overlay - -def grove01_well(): - global grove01_drinking, grove01_well_timer - if grove01_drinking == False: - grove01_drinking = True - - grove01_well_timer = new_timer() - grove01_well_timer.running = True - - sound_splash() - +import areas.sounds +#import stdlib.ai.wander + +ai_wizard = None +drinking = False +duration = 1.0 +max_alpha = 192 +well_timer = None + +def load(): + #ai_wizard = stdlib_ai_wander("entities/wizard/wizard.xml", "down", 6, 3, 0.0, 1.0, 4) + + cloud = Area.new_overlay("entities/cloud/cloud.xml", 11, 2, 10.0, "down") + cloud.move(-400, 0) + +def tick(): + global drinking, duration, max_alpha + if drinking == True: + progress = well_timer.count / duration + if progress < 0.5: + overlay_alpha = int(2 * max_alpha * progress) + elif progress < 1.0: + overlay_alpha = int(2 * max_alpha * (1 - progress)) + else: + overlay_alpha = 0 + drinking = False + Area.color_overlay(255, 255, 255, overlay_alpha) # white overlay + +def well(): + global drinking, well_timer + if drinking == False: + drinking = True + + well_timer = new_timer() + well_timer.running = True + + areas.sounds.sound_splash() + diff --git a/data/testing/areas/grove01.tmx b/data/testing/areas/grove01.tmx index 0016ca4..0d6fdf9 100644 --- a/data/testing/areas/grove01.tmx +++ b/data/testing/areas/grove01.tmx @@ -3,8 +3,8 @@ - - + + @@ -258,7 +258,7 @@ - + diff --git a/data/testing/areas/grove02.py b/data/testing/areas/grove02.py dissimilarity index 63% index ebcae4e..abd4944 100644 --- a/data/testing/areas/grove02.py +++ b/data/testing/areas/grove02.py @@ -1,26 +1,30 @@ -# This script is called when the chest in grove02.tmx is activated by the -# player. The first time we are run, we open the chest. Further invocations -# feature an easter egg where we toggle the game's music. :) - -def grove02_toggle_music(): - if Music.paused: - Music.paused = False - print 'Unpausing music!' - else: - Music.paused = True - print 'Pausing music!' - -# Open the chest! -def grove02_open_chest(): - global grove02_opened_chest - if not grove02_opened_chest: - grove02_opened_chest = True - tile = Area.tile(5, 2, -0.05) # closed chest - tile2 = tile.offset(0, -1) # above the closed chest - tile.type = Area.tileset("areas/tiles/objects.png").at(1, 6) # change to open chest, button half - tile2.type = Area.tileset("areas/tiles/objects.png").at(1, 5) # change to open chest, top half - Area.redraw() - Sound.play("sounds/door.oga") # unlocking sound - else: - grove02_toggle_music() - +import areas.sounds + +opened_chest = False + +def toggle_music(): +# if Music.paused: +# Music.paused = False +# log('Unpausing music!') +# else: +# Music.paused = True +# log('Pausing music!') + pass + +def open_chest(): + # This function is called when the chest in grove02.tmx is activated by + # the player. The first time we are run, we open the chest. Further + # invocations feature an easter egg where we toggle the game's music. :) + + global opened_chest + if not opened_chest: + opened_chest = True + tile = Area.tile(5, 2, -0.05) # closed chest + tile2 = tile.offset(0, -1) # above the closed chest + tile.type = Area.tileset("areas/tiles/objects.png").at(1, 6) # change to open chest, button half + tile2.type = Area.tileset("areas/tiles/objects.png").at(1, 5) # change to open chest, top half + Area.redraw() + areas.sounds.sound_door() + else: + toggle_music() + diff --git a/data/testing/areas/grove02.tmx b/data/testing/areas/grove02.tmx index d4afec4..257ad5a 100644 --- a/data/testing/areas/grove02.tmx +++ b/data/testing/areas/grove02.tmx @@ -368,7 +368,7 @@ - + diff --git a/data/testing/areas/grove_house.py b/data/testing/areas/grove_house.py index 46160f5..4aa4d18 100644 --- a/data/testing/areas/grove_house.py +++ b/data/testing/areas/grove_house.py @@ -1,8 +1,12 @@ -# Unlock the door! -def house_open_door(): - global house_opened_door - if house_opened_door == False: - house_opened_door = True +import areas.sounds + +opened_door = False + +def open_door(): + # Unlock the door! + global opened_door + if opened_door == False: + opened_door = True tile = Area.tile(4, 0, 0.0) # closed exit on north wall, property layer tile.exit = new_exit("areas/secret_room.tmx", 4, 5, 0.0) @@ -12,5 +16,5 @@ def house_open_door(): tile.type = Area.tileset("areas/tiles/indoors.png").at(2, 9) # change to open exit Area.redraw() - Sound.play("sounds/door.oga") # unlocking sound + areas.sounds.sound_door() diff --git a/data/testing/areas/grove_house.tmx b/data/testing/areas/grove_house.tmx index 79c72d3..9dd0f1f 100644 --- a/data/testing/areas/grove_house.tmx +++ b/data/testing/areas/grove_house.tmx @@ -363,7 +363,7 @@ - + @@ -373,7 +373,7 @@ - + @@ -384,12 +384,12 @@ - + - + diff --git a/data/testing/areas/secret_room.tmx b/data/testing/areas/secret_room.tmx index 922b615..3c31993 100644 --- a/data/testing/areas/secret_room.tmx +++ b/data/testing/areas/secret_room.tmx @@ -257,17 +257,17 @@ - + - + - + diff --git a/data/testing/areas/sounds.py b/data/testing/areas/sounds.py index 7564178..4904a04 100644 --- a/data/testing/areas/sounds.py +++ b/data/testing/areas/sounds.py @@ -6,6 +6,10 @@ def sound_book(): snd = Sound.play("sounds/book.oga") # book sound snd.speed = 1.0 + randfloat(-0.3, 0.3) +def sound_door(): + snd = Sound.play("sounds/door.oga") # door opening sound + snd.speed = 1.0 + randfloat(-0.3, 0.3) + def sound_ouch(): snd = Sound.play("sounds/ouch.oga") # ouch sound snd.speed = 1.0 + randfloat(-0.1, 0.1) diff --git a/data/testing/init_area.py b/data/testing/init_area.py index e69de29..ff82958 100644 --- a/data/testing/init_area.py +++ b/data/testing/init_area.py @@ -0,0 +1,2 @@ +def execute(): + pass \ No newline at end of file diff --git a/data/testing/init_world.py b/data/testing/init_world.py index d5e7bcc..ff82958 100644 --- a/data/testing/init_world.py +++ b/data/testing/init_world.py @@ -1,17 +1,2 @@ -import areas.sounds, stdlib.ai.wander - -grove01_ai_wizard = None -grove01_drinking = False -grove01_duration = 1.0 -grove01_max_alpha = 192 -grove01_well_timer = None - -grove02_opened_chest = False - -house_opened_door = False - -cave_fall_gfx_timer = None -cave_fall_snd = None -cave_fall_stage = 0 -cave_overlay_alpha = 0 - +def execute(): + pass \ No newline at end of file diff --git a/data/testing/world.conf b/data/testing/world.conf index c74e539..eefb407 100644 --- a/data/testing/world.conf +++ b/data/testing/world.conf @@ -13,8 +13,8 @@ diff --git a/src/area-tmx.cpp b/src/area-tmx.cpp index 93ac2f9..f4aa52d 100644 --- a/src/area-tmx.cpp +++ b/src/area-tmx.cpp @@ -153,29 +153,29 @@ bool AreaTMX::processMapProperties(XMLNode node) } else if (name == "on_load") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; loadScript = script; } else if (name == "on_focus") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; focusScript = script; } else if (name == "on_tick") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; tickScript = script; } else if (name == "on_turn") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; turnScript = script; } @@ -337,22 +337,22 @@ bool AreaTMX::processTileType(XMLNode node, TileType& type, } else if (name == "on_enter") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; type.enterScript = script; } else if (name == "on_leave") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; type.leaveScript = script; } else if (name == "on_use") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; type.useScript = script; } @@ -629,9 +629,9 @@ bool AreaTMX::processObject(XMLNode node, int z) // Gather object properties now. Assign them to tiles later. bool wwide[5], hwide[5]; /* wide exit in dimensions: width, height */ - boost::optional enterScript, leaveScript, useScript; + ScriptRef enterScript, leaveScript, useScript; boost::scoped_ptr exit[5]; - boost::optional layermods[5]; + boost::scoped_ptr layermods[5]; unsigned flags = 0x0; XMLNode child = node.childrenNode(); // @@ -648,22 +648,22 @@ bool AreaTMX::processObject(XMLNode node, int z) } else if (name == "on_enter") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; enterScript = script; } else if (name == "on_leave") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; leaveScript = script; } else if (name == "on_use") { std::string filename = value; - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; useScript = script; } @@ -691,28 +691,28 @@ bool AreaTMX::processObject(XMLNode node, int z) else if (name == "layermod") { double mod; ASSERT(child.doubleAttr("value", &mod)); - layermods[EXIT_NORMAL].reset(mod); + layermods[EXIT_NORMAL].reset(new double(mod)); flags |= TILE_NOWALK_NPC; } else if (name == "layermod:up") { double mod; ASSERT(child.doubleAttr("value", &mod)); - layermods[EXIT_UP].reset(mod); + layermods[EXIT_UP].reset(new double(mod)); } else if (name == "layermod:down") { double mod; ASSERT(child.doubleAttr("value", &mod)); - layermods[EXIT_DOWN].reset(mod); + layermods[EXIT_DOWN].reset(new double(mod)); } else if (name == "layermod:left") { double mod; ASSERT(child.doubleAttr("value", &mod)); - layermods[EXIT_LEFT].reset(mod); + layermods[EXIT_LEFT].reset(new double(mod)); } else if (name == "layermod:right") { double mod; ASSERT(child.doubleAttr("value", &mod)); - layermods[EXIT_RIGHT].reset(mod); + layermods[EXIT_RIGHT].reset(new double(mod)); } } @@ -766,8 +766,7 @@ bool AreaTMX::processObject(XMLNode node, int z) } } for (int i = 0; i < 5; i++) - if (layermods[i]) - tile.layermods[i] = layermods[i]; + tile.layermods[i] = layermods[i] ? new double(*layermods[i].get()) : NULL; tile.enterScript = enterScript; tile.leaveScript = leaveScript; tile.useScript = useScript; diff --git a/src/area.cpp b/src/area.cpp index 1df0186..134334e 100644 --- a/src/area.cpp +++ b/src/area.cpp @@ -44,6 +44,7 @@ #include "npc.h" #include "overlay.h" #include "python.h" +#include "python-bindings-template.cpp" #include "reader.h" #include "tile.h" #include "window.h" @@ -653,9 +654,9 @@ void exportArea() return_value_policy()) .def("new_overlay", &Area::spawnOverlay, return_value_policy()) - .def_readwrite("on_focus", &Area::focusScript) - .def_readwrite("on_tick", &Area::tickScript) - .def_readwrite("on_turn", &Area::turnScript) +// .def_readwrite("on_focus", &Area::focusScript) +// .def_readwrite("on_tick", &Area::tickScript) +// .def_readwrite("on_turn", &Area::turnScript) ; } diff --git a/src/area.h b/src/area.h index cfa7fbd..ba90e0c 100644 --- a/src/area.h +++ b/src/area.h @@ -38,7 +38,7 @@ #include #include "entity.h" -#include "scriptinst.h" +#include "script.h" #include "tile.h" #include "vec.h" @@ -176,7 +176,7 @@ public: // // Script hooks. - boost::optional loadScript, focusScript, tickScript, turnScript; + ScriptRef loadScript, focusScript, tickScript, turnScript; protected: diff --git a/src/backend-gosu/gosu-cbuffer.h b/src/backend-gosu/gosu-cbuffer.h index 1eaad90..8e09fe6 100644 --- a/src/backend-gosu/gosu-cbuffer.h +++ b/src/backend-gosu/gosu-cbuffer.h @@ -24,8 +24,8 @@ // IN THE SOFTWARE. // ********** -#ifndef CBUFFER_H -#define CBUFFER_H +#ifndef GOSU_CBUFFER_H +#define GOSU_CBUFFER_H #include diff --git a/src/backend-gosu/gosu-image.cpp b/src/backend-gosu/gosu-image.cpp index 3730707..ebe741b 100644 --- a/src/backend-gosu/gosu-image.cpp +++ b/src/backend-gosu/gosu-image.cpp @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** image-impl.cpp ** +** gosu-image.cpp ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ diff --git a/src/backend-gosu/gosu-image.h b/src/backend-gosu/gosu-image.h index 1d807ad..30fdc11 100644 --- a/src/backend-gosu/gosu-image.h +++ b/src/backend-gosu/gosu-image.h @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** image-impl.h ** +** gosu-image.h ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ @@ -24,8 +24,8 @@ // IN THE SOFTWARE. // ********** -#ifndef IMAGE_IMPL_H -#define IMAGE_IMPL_H +#ifndef GOSU_IMAGE_H +#define GOSU_IMAGE_H #include "image.h" diff --git a/src/bytecode.cpp b/src/bytecode.cpp deleted file mode 100644 index 84d4de0..0000000 --- a/src/bytecode.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/*************************************** -** Tsunagari Tile Engine ** -** python.cpp ** -** Copyright 2011-2013 PariahSoft LLC ** -***************************************/ - -// ********** -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -// ********** - - -#ifdef __APPLE__ -#include -#endif - -#include -#include -#include - -#include "bytecode.h" -#include "log.h" -#include "python.h" - - -static PyCodeObject* compile(const std::string& fn, const std::string& code) -{ - PyArena *arena = PyArena_New(); - if (!arena) { - Log::err("Python", fn + ": failed to create arena"); - return NULL; - } - - mod_ty mod = PyParser_ASTFromString(code.c_str(), fn.c_str(), - Py_file_input, NULL, arena); - if (!mod) { - Log::err("Python", fn + ": failed to parse"); - pythonErr(); - return NULL; - } - - PyCodeObject* co = PyAST_Compile(mod, fn.c_str(), NULL, arena); - if (!co) { - Log::err("Python", fn + ": failed to compile"); - pythonErr(); - return NULL; - } - - PyArena_Free(arena); - return co; -} - - -static bool executeBytecode(PyCodeObject* code, PyObject* globals, PyObject* locals) -{ - assert(code && globals && locals); - - inPythonScript++; - PyObject* result = PyEval_EvalCode(code, globals, locals); - inPythonScript--; - - if (!result) - pythonErr(); - return result; -} - - -Bytecode::Bytecode(const std::string& filename, - const std::string& sourcecode) - : fn(filename), - code(compile(filename, sourcecode)), - locals(PyDict_New()) -{ -} - - -Bytecode::~Bytecode() -{ - Py_XDECREF(code); - Py_XDECREF(locals); -} - - -bool Bytecode::execute() -{ - assert(valid()); - - PyObject* globals = pythonGlobals(); - Log::err("foo", PyString_AsString(PyObject_Repr(globals))); - return executeBytecode(code, globals, locals); -} - - -bool Bytecode::valid() const -{ - return code && locals; -} - - -const std::string& Bytecode::filename() const -{ - return fn; -} - diff --git a/src/bytecode.h b/src/bytecode.h deleted file mode 100644 index 295b94b..0000000 --- a/src/bytecode.h +++ /dev/null @@ -1,67 +0,0 @@ -/*************************************** -** Tsunagari Tile Engine ** -** python-script.h ** -** Copyright 2011-2013 PariahSoft LLC ** -***************************************/ - -// ********** -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -// ********** - -#ifndef PYTHON_SCRIPT_H -#define PYTHON_SCRIPT_H - -#include - -#include - -// from Python -#include -#include -#include - -class Bytecode -{ -public: - //! Compile a Python script. Must provide both a representative - //! filename for any error messages along with a string containing the - //! body of code to compile. Prints any errors on failure. - Bytecode(const std::string& filename, const std::string& sourcecode); - ~Bytecode(); - - //! Run a compiled Python script. Returns false on runtime error and - //! prints the error. - bool execute(); - - //! Returns true if this object was constructed correctly. - bool valid() const; - - const std::string& filename() const; - -private: - std::string fn; - - PyCodeObject* code; - PyObject* locals; -}; - -typedef boost::shared_ptr BytecodeRef; - -#endif - diff --git a/src/entity.cpp b/src/entity.cpp index b42c420..6075494 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -36,6 +36,7 @@ #include "entity.h" #include "log.h" #include "python.h" +#include "python-bindings-template.cpp" #include "reader.h" #include "string.h" #include "world.h" @@ -529,7 +530,7 @@ void Entity::postMove() moving = false; if (destTile) { - boost::optional layermod = destTile->layermods[EXIT_NORMAL]; + double* layermod = destTile->layermods[EXIT_NORMAL]; if (layermod) r.z = *layermod; } @@ -776,8 +777,8 @@ bool Entity::processScript(const XMLNode node) return false; } - ScriptInst script(filename); - if (!script.validate()) + ScriptRef script = Script::create(filename); + if (!script || !script->validate()) return false; if (!setScript(trigger, script)) { @@ -789,7 +790,7 @@ bool Entity::processScript(const XMLNode node) return true; } -bool Entity::setScript(const std::string& trigger, ScriptInst& script) +bool Entity::setScript(const std::string& trigger, ScriptRef& script) { if (boost::iequals(trigger, "on_tick")) { tickScript = script; @@ -846,11 +847,11 @@ void exportEntity() .def("can_move", static_cast (&Entity::canMove)) - .def_readwrite("on_tick", &Entity::tickScript) - .def_readwrite("on_turn", &Entity::turnScript) - .def_readwrite("on_tile_entry", &Entity::tileEntryScript) - .def_readwrite("on_tile_exit", &Entity::tileExitScript) - .def_readwrite("on_delete", &Entity::deleteScript) +// .def_readwrite("on_tick", &Entity::tickScript) +// .def_readwrite("on_turn", &Entity::turnScript) +// .def_readwrite("on_tile_entry", &Entity::tileEntryScript) +// .def_readwrite("on_tile_exit", &Entity::tileExitScript) +// .def_readwrite("on_delete", &Entity::deleteScript) ; } diff --git a/src/entity.h b/src/entity.h index cad16d1..5751761 100644 --- a/src/entity.h +++ b/src/entity.h @@ -37,8 +37,7 @@ class Entity; #include "tile.h" // for enum TileEventTrigger #include "reader.h" -#include "scriptinst.h" -#include "python.h" +#include "script.h" class Animation; class Area; @@ -175,7 +174,7 @@ public: // //! Script hooks. - boost::optional tickScript, turnScript, tileEntryScript, + ScriptRef tickScript, turnScript, tileEntryScript, tileExitScript, deleteScript; @@ -228,7 +227,7 @@ protected: bool processSound(const XMLNode node); bool processScripts(XMLNode node); bool processScript(const XMLNode node); - bool setScript(const std::string& trigger, ScriptInst& script); + bool setScript(const std::string& trigger, ScriptRef& script); protected: diff --git a/src/formatter.cpp b/src/formatter.cpp index 72bb814..e35e2d3 100644 --- a/src/formatter.cpp +++ b/src/formatter.cpp @@ -24,18 +24,22 @@ // IN THE SOFTWARE. // ********** + #include "formatter.h" + Formatter::Formatter(std::string format) : result(format), pos(0) { findNextPlaceholder(); } + Formatter::~Formatter() { } + Formatter::operator const std::string&() { assert(pos == result.size()); @@ -43,6 +47,7 @@ Formatter::operator const std::string&() return result; } + void Formatter::findNextPlaceholder() { assert(pos <= result.size()); @@ -56,30 +61,67 @@ void Formatter::findNextPlaceholder() template<> -std::string Formatter::format(const int& data) +std::string Formatter::format(int data) { char buf[512]; - sprintf(buf, "%i", data); + sprintf(buf, "%d", data); return std::string(buf); } + template<> -std::string Formatter::format(const double& data) +std::string Formatter::format(unsigned int data) +{ + char buf[512]; + sprintf(buf, "%u", data); + return std::string(buf); +} + + +template<> +std::string Formatter::format(long data) +{ + char buf[512]; + sprintf(buf, "%ld", data); + return std::string(buf); +} + + +template<> +std::string Formatter::format(double data) { char buf[512]; sprintf(buf, "%f", data); return std::string(buf); } -typedef char* cstring; + template<> -std::string Formatter::format(const cstring& data) +std::string Formatter::format(const char* data) { return std::string(data); } + template<> std::string Formatter::format(const std::string& data) { return data; -} \ No newline at end of file +} + + +template<> +std::string Formatter::format(const std::string data) +{ + return data; +} + + +template<> +std::string Formatter::format(void* data) +{ + char buf[512]; + sprintf(buf, "%p", data); + return std::string(buf); +} + diff --git a/src/formatter.h b/src/formatter.h index 7734602..153041d 100644 --- a/src/formatter.h +++ b/src/formatter.h @@ -42,7 +42,7 @@ public: ~Formatter(); template - Formatter& operator %(const T& data) + Formatter& operator %(T data) { assert(pos < result.size()); @@ -56,8 +56,8 @@ public: private: template - std::string format(const T& data); - + std::string format(const T data); + void findNextPlaceholder(); std::string result; diff --git a/src/tiledimage-impl.cpp b/src/gosu-tiledimage.cpp similarity index 96% rename from src/tiledimage-impl.cpp rename to src/gosu-tiledimage.cpp index 506b304..f932fd7 100644 --- a/src/tiledimage-impl.cpp +++ b/src/gosu-tiledimage.cpp @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** tiledimage-impl.cpp ** +** gosu-tiledimage.cpp ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ @@ -27,8 +27,8 @@ #include #include "gosu-cbuffer.h" -#include "image-impl.h" -#include "tiledimage-impl.h" +#include "gosu-image.h" +#include "gosu-tiledimage.h" #include "window.h" TiledImage* TiledImage::create(void* data, size_t length, diff --git a/src/tiledimage-impl.h b/src/gosu-tiledimage.h similarity index 94% copy from src/tiledimage-impl.h copy to src/gosu-tiledimage.h index c0de97c..54ea432 100644 --- a/src/tiledimage-impl.h +++ b/src/gosu-tiledimage.h @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** tiledimage-impl.h ** +** gosu-tiledimage.h ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ @@ -24,8 +24,8 @@ // IN THE SOFTWARE. // ********** -#ifndef TILEDIMAGE_IMPL_H -#define TILEDIMAGE_IMPL_H +#ifndef GOSU_TILEDIMAGE_H +#define GOSU_TILEDIMAGE_H #include diff --git a/src/image.cpp b/src/image.cpp index 1bdc253..64264b9 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -28,3 +28,4 @@ Image::Image() { } Image::~Image() { } + diff --git a/src/log.cpp b/src/log.cpp index 20342d1..11481e0 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -31,10 +31,10 @@ #include "client-conf.h" #include "log.h" #include "python.h" +#include "python-bindings-template.cpp" #include "world.h" #ifdef _WIN32 - #include #include "os-windows.h" #endif @@ -148,8 +148,6 @@ static void pythonLogInfo(std::string msg) void exportLog() { - using namespace boost::python; - pythonAddFunction("log", pythonLogInfo); } diff --git a/src/music.cpp b/src/music.cpp index af0905d..b3f8176 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -34,6 +34,7 @@ #include "reader.h" #include "readercache.h" #include "python.h" +#include "python-bindings-template.cpp" typedef boost::shared_ptr SongRef; @@ -269,7 +270,6 @@ void Music::tick() void exportMusic() { - // FIXME: Broken with shift to singleton. No instantiated object to bind. // Fix will require a stub object. diff --git a/src/scriptinst.h b/src/python-bindings-template.cpp similarity index 54% rename from src/scriptinst.h rename to src/python-bindings-template.cpp index 3adaf9f..3a6f321 100644 --- a/src/scriptinst.h +++ b/src/python-bindings-template.cpp @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** scriptinst.h ** +** python-bindings-template.h ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ @@ -24,40 +24,53 @@ // IN THE SOFTWARE. // ********** -#ifndef SCRIPTINST_H -#define SCRIPTINST_H - -#include +#include // Including this fixes a compilation-order error on + // XCode 4.6 +// In this file. +#include +#include +#include #include -#include +#include +#include -#include "bytecode.h" +// For bindings. +#include +#include +#include +#include +#include "python.h" -class ScriptInst +//! Bind a C++ object into the global Python namespace. +template +void pythonSetGlobal(const std::string& name, T pointer) { -public: - ScriptInst(const std::string& source); - ScriptInst(boost::python::object callable); - - bool validate(); - bool invoke(); + using namespace boost::python; -private: - boost::variant< - BytecodeRef, - boost::python::object - > data; + object obj(ptr(pointer)); + PyObject* globals = NULL; - friend struct validate_visitor; - friend struct invoke_visitor; - friend struct topython_visitor; - friend struct scriptinst_to_python; -}; + if ((globals = pythonGlobals()) == NULL) + goto err; + PyDict_SetItemString(globals, name.c_str(), obj.ptr()); + return; -void exportScriptInst(); - -#endif +err: + pythonErr(); +} +template +void pythonAddFunction(const std::string& name, Fn fn) +{ + using namespace boost::python; + + try { + scope bltins(import("__builtin__")); + def(name.c_str(), fn); + } catch (error_already_set) { + pythonErr(); + } +} diff --git a/src/python-bindings.cpp b/src/python-bindings.cpp index bac905b..6f9bd88 100644 --- a/src/python-bindings.cpp +++ b/src/python-bindings.cpp @@ -32,7 +32,7 @@ #include "music.h" #include "random.h" #include "reader.h" -#include "scriptinst.h" +#include "script.h" #include "sound.h" #include "tile.h" #include "timer.h" @@ -48,7 +48,7 @@ BOOST_PYTHON_MODULE(tsunagari) exportMusic(); exportRandom(); exportReader(); - exportScriptInst(); + exportScript(); exportSound(); exportTile(); exportTimeout(); diff --git a/src/pyworldfinder.cpp b/src/python-importer.cpp similarity index 69% rename from src/pyworldfinder.cpp rename to src/python-importer.cpp index 1eb8455..f759b2e 100644 --- a/src/pyworldfinder.cpp +++ b/src/python-importer.cpp @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** pyworldfinder.cpp ** +** python-importer.cpp ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ @@ -27,8 +27,10 @@ #include +#include "formatter.h" #include "log.h" #include "reader.h" +#include "python-importer.h" //! List of known safe Python modules allowed for importing. @@ -40,7 +42,7 @@ static const char* module_whitelist[] = { NULL }; -static bool wf_in_whitelist(const char* name) +static bool wi_in_whitelist(const char* name) { for (int i = 0; module_whitelist[i]; i++) if (!strcmp(name, module_whitelist[i])) @@ -48,7 +50,7 @@ static bool wf_in_whitelist(const char* name) return false; } -static PyObject* wf_find_module(PyObject* /*self*/, PyObject* args) +static PyObject* wi_find_module(PyObject* /*self*/, PyObject* args) { PyObject* fullname = NULL; PyObject* path = NULL; // dummy @@ -67,8 +69,10 @@ static PyObject* wf_find_module(PyObject* /*self*/, PyObject* args) std::string slashname(dotname); // "foo/bar.py" std::replace(slashname.begin(), slashname.end(), '.', '/'); + Log::info("PyWorldImporter", Formatter("%: requested") % dotname); + // Returning Py_None allows the import to continue. NULL stops it. - if (wf_in_whitelist(dotname)) { + if (wi_in_whitelist(dotname)) { Py_INCREF(Py_None); return Py_None; } @@ -88,78 +92,78 @@ static PyObject* wf_find_module(PyObject* /*self*/, PyObject* args) typedef struct { PyObject_HEAD -} worldfinderobject; +} worldimporterobject; -static PyMethodDef pyworldfinder_methods[] = { - {"find_module", (PyCFunction)wf_find_module, METH_VARARGS, NULL}, +static PyMethodDef pyworldimporter_methods[] = { + {"find_module", (PyCFunction)wi_find_module, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL}, }; static bool initted = false; -static PyTypeObject worldfinder_type = { +static PyTypeObject worldimporter_type = { PyObject_HEAD_INIT(&PyType_Type) }; -static bool wf_init_type() +static bool wi_init_type() { if (!initted) { initted = false; - worldfinder_type.tp_name = "worldfinder"; - worldfinder_type.tp_basicsize = sizeof(worldfinderobject); - worldfinder_type.tp_getattro = PyObject_GenericGetAttr; - worldfinder_type.tp_flags = Py_TPFLAGS_DEFAULT; - worldfinder_type.tp_methods = pyworldfinder_methods; + worldimporter_type.tp_name = "worldimporter"; + worldimporter_type.tp_basicsize = sizeof(worldimporterobject); + worldimporter_type.tp_getattro = PyObject_GenericGetAttr; + worldimporter_type.tp_flags = Py_TPFLAGS_DEFAULT; + worldimporter_type.tp_methods = pyworldimporter_methods; - if (PyType_Ready(&worldfinder_type) < 0) { - Py_FatalError("Can't initialize worldfinder type"); + if (PyType_Ready(&worldimporter_type) < 0) { + Py_FatalError("Can't initialize worldimporter type"); return false; } } return true; } -static PyObject* wf_worldfinder_new() +static PyObject* wi_worldimporter_new() { - wf_init_type(); - return (PyObject*)PyObject_New(worldfinderobject, &worldfinder_type); + wi_init_type(); + return (PyObject*)PyObject_New(worldimporterobject, &worldimporter_type); } /** Public functions **/ -bool add_worldfinder() +bool pythonImporterInstall() { PyObject* meta_path = NULL; - PyObject* finder = NULL; + PyObject* importer = NULL; int idx = -1; /* meta_path is a borrowed reference; no decref */ meta_path = PySys_GetObject((char*)"meta_path"); if (meta_path == NULL || !PyList_Check(meta_path)) { - Log::fatal("Python", + Log::fatal("Python Importer", "sys.meta_path must be a list of import hooks"); goto err; } - finder = wf_worldfinder_new(); - if (finder == NULL) { - Log::fatal("Python", - "failed to create PyWorldFinder object"); + importer = wi_worldimporter_new(); + if (importer == NULL) { + Log::fatal("Python Importer", + "failed to create PyWorldImporter object"); goto err; } - idx = PyList_Append(meta_path, finder); + idx = PyList_Append(meta_path, importer); if (idx == -1) { - Log::fatal("Python", "failed to append to sys.meta_path"); + Log::fatal("Python Importer", "failed to append to sys.meta_path"); goto err; } - Py_DECREF(finder); + Py_DECREF(importer); return true; err: - Py_XDECREF(finder); + Py_XDECREF(importer); return false; } diff --git a/src/pyworldfinder.h b/src/python-importer.h similarity index 92% rename from src/pyworldfinder.h rename to src/python-importer.h index f9be594..5774616 100644 --- a/src/pyworldfinder.h +++ b/src/python-importer.h @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** pyworldfinder.h ** +** python-importer.h ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ @@ -24,6 +24,9 @@ // IN THE SOFTWARE. // ********** +#ifndef PYTHON_IMPORTER_H +#define PYTHON_IMPORTER_H + /* Install a filter to Python's import statement that blocks illegal modules * from being imported. All modules inside a world file are allowed, as are * select packages from Python's base installation. Others are denied and will @@ -32,4 +35,7 @@ * This function does not add world files to Python's import path. This needs * to happen seperately. */ -bool add_worldfinder(); +bool pythonImporterInstall(); + +#endif + diff --git a/src/python.cpp b/src/python.cpp index cac5b57..a72e604 100644 --- a/src/python.cpp +++ b/src/python.cpp @@ -38,7 +38,7 @@ #include "log.h" #include "python.h" #include "python-bindings.h" // for pythonInitBindings -#include "pyworldfinder.h" +#include "python-importer.h" #include "reader.h" #include "window.h" @@ -47,8 +47,8 @@ namespace bp = boost::python; -static PyObject* mainModule; -static PyObject* mainDict; +//static PyObject* mainModule; +//static PyObject* mainDict; int inPythonScript = 0; @@ -97,14 +97,12 @@ PyMethodDef nullMethods[] = { {NULL, NULL, 0, NULL} }; -static bool sysPathAppend(const std::string& path) +bool pythonPrependPath(const std::string& path) { PyObject* pypath = NULL; PyObject* syspath = NULL; - int idx = -1; - pypath = PyString_FromString(path.c_str()); - if (pypath == NULL) + if ((pypath = PyString_FromString(path.c_str())) == NULL) goto err; syspath = PySys_GetObject((char*)"path"); @@ -114,8 +112,7 @@ static bool sysPathAppend(const std::string& path) goto err; } - idx = PyList_Append(syspath, pypath); - if (idx == -1) { + if (PyList_Insert(syspath, 0, pypath) == -1) { Log::fatal("Python", "failed to append to sys.path"); goto err; } @@ -128,51 +125,62 @@ err: return false; } + +bool pythonRmPath(const std::string& path) +{ + PyObject* pypath = NULL; + PyObject* syspath = NULL; + + if ((pypath = PyString_FromString(path.c_str())) == NULL) + goto err; + + syspath = PySys_GetObject((char*)"path"); + if (syspath == NULL || !PyList_Check(syspath)) { + Log::fatal("Python", + "sys.path must be a list of strings"); + goto err; + } + + if (PyList_SetSlice(syspath, 0, 1, NULL) == -1) { + Log::err("Python", "failed to pop sys.path"); + goto err; + } + + Py_DECREF(pypath); + return true; + +err: + Py_XDECREF(pypath); + return false; +} + + + bool pythonInit() { - PyObject* name = NULL; - PyObject* tsuModule = NULL; + PyObject* module = NULL; PyImport_AppendInittab("tsunagari", &pythonInitBindings); Py_InitializeEx(0); + if ((module = PyImport_ImportModule("tsunagari")) == NULL) + goto err; + Py_DECREF(module); + if (PyUnicode_SetDefaultEncoding("utf-8")) { PyErr_Format(PyExc_SystemError, "encoding %s not found", "utf-8"); goto err; } - if ((mainModule = PyImport_ImportModule("__main__")) == NULL) - goto err; - if ((mainDict = PyModule_GetDict(mainModule)) == NULL) - goto err; - - if ((name = PyString_FromString("tsunagari")) == NULL) - goto err; - if ((tsuModule = PyImport_ImportModule("tsunagari")) == NULL) - goto err; - if (PyObject_SetAttr(mainModule, name, tsuModule) < 0) - goto err; - - Py_DECREF(name); - Py_DECREF(tsuModule); - // Disable builtin filesystem IO. if (Py_InitModule("__builtin__", nullMethods) == NULL) goto err; // Disable most Python system imports. - if (!add_worldfinder()) + if (!pythonImporterInstall()) goto err; - // Add world to Python's sys.path. - if (!sysPathAppend(BASE_ZIP_PATH)) - goto err; - - for (Conf::StringVector::iterator it = conf.dataPath.begin(); it != conf.dataPath.end(); it++) - if (!sysPathAppend(*it)) - goto err; - return true; err: @@ -185,7 +193,7 @@ err: void pythonFinalize() { - Py_DECREF(mainModule); +// Py_DECREF(mainModule); Py_Finalize(); } @@ -267,6 +275,22 @@ void pythonErr() PyObject* pythonGlobals() { - return mainDict; + PyObject* bltins = NULL, *dict = NULL; + + if ((bltins = PyImport_ImportModule("__builtin__")) == NULL) + goto err; + if ((dict = PyModule_GetDict(bltins)) == NULL) + goto err; + Py_DECREF(bltins); + return dict; + +err: + Py_XDECREF(bltins); + return NULL; } + +void pythonDumpGlobals() +{ + Log::info("globals-state", PyString_AsString(PyObject_Repr(pythonGlobals()))); +} diff --git a/src/python.h b/src/python.h index cae2d1f..a6ad909 100644 --- a/src/python.h +++ b/src/python.h @@ -27,22 +27,9 @@ #ifndef PYTHON_H #define PYTHON_H -// In this file. -#include -#include -#include -#include -#include -#include - -// For bindings. -#include -#include -#include -#include - #include +typedef struct _object PyObject; extern int inPythonScript; @@ -57,47 +44,21 @@ void pythonFinalize(); void pythonErr(); +bool pythonPrependPath(const std::string& path); +bool pythonRmPath(const std::string& path); + + //! Access to global namespace shared by all Python scripts. PyObject* pythonGlobals(); //! Bind a C++ object into the global Python namespace. template -void pythonSetGlobal(const std::string& name, T pointer) -{ - using namespace boost::python; - - PyObject* globals = NULL; - PyObject* wrapper = NULL; - - if ((globals = pythonGlobals()) == NULL) - goto err; - try { - wrapper = incref(converter::arg_to_python(pointer).get()); - } catch (boost::python::error_already_set) { - goto err; - } - - PyDict_SetItemString(globals, name.c_str(), wrapper); - Py_DECREF(wrapper); - - return; - -err: - pythonErr(); -} +void pythonSetGlobal(const std::string& name, T pointer); template -void pythonAddFunction(const std::string& name, Fn fn) -{ - using namespace boost::python; - - try { - scope bltins(import("__builtin__")); - def(name.c_str(), fn); - } catch (error_already_set) { - pythonErr(); - } -} +void pythonAddFunction(const std::string& name, Fn fn); + +void pythonDumpGlobals(); #endif diff --git a/src/random.cpp b/src/random.cpp index e516303..05b68c0 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -25,6 +25,7 @@ // ********** #include "python.h" +#include "python-bindings-template.cpp" #include "random.h" int randInt(int min, int max) diff --git a/src/reader.cpp b/src/reader.cpp index 6629366..eff4acc 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -27,8 +27,6 @@ #include #include -#include -#include #include #include #include @@ -37,12 +35,14 @@ #include #include -#include "bytecode.h" #include "cache-template.cpp" #include "client-conf.h" +#include "formatter.h" #include "log.h" #include "python.h" +#include "python-bindings-template.cpp" #include "reader.h" +#include "script.h" #include "window.h" #include "xml.h" @@ -57,7 +57,6 @@ Cache images; Cache tiles; Cache sounds; Cache xmls; -Cache bytecodes; Cache texts; // DTDs don't expire. No garbage collection. @@ -75,37 +74,34 @@ static std::string path(const std::string& entryName) template static bool readFromDisk(const std::string& name, T& buf) { - using namespace boost; - PHYSFS_sint64 size; PHYSFS_File* zf; if (!PHYSFS_exists(name.c_str())) { - Log::err("Reader", str(format("%s: file missing") - % path(name))); + Log::err("Reader", Formatter("%: file missing") + % path(name)); return false; } zf = PHYSFS_openRead(name.c_str()); if (!zf) { - Log::err("Reader", str(format("%s: error opening file: %s") - % path(name) % PHYSFS_getLastError())); + Log::err("Reader", Formatter("%: error opening file: %") + % path(name) % PHYSFS_getLastError()); return false; } size = PHYSFS_fileLength(zf); if (size == -1) { - Log::err("Reader", str( - format("%s: could not determine file size: %s") - % path(name) % PHYSFS_getLastError())); + Log::err("Reader", Formatter("%: could not determine file size: %") + % path(name) % PHYSFS_getLastError()); PHYSFS_close(zf); return false; } if (size > std::numeric_limits::max()) { // FIXME: Technically, we just need to issue multiple calls to // PHYSFS_read. Fix when needed. - Log::err("Reader", str(format("%s: file too long (>4GB)") - % path(name))); + Log::err("Reader", Formatter("%: file too long (>4GB)") + % path(name)); PHYSFS_close(zf); return false; } @@ -118,8 +114,8 @@ static bool readFromDisk(const std::string& name, T& buf) if (PHYSFS_read(zf, (char*)(buf.data()), (PHYSFS_uint32)size, 1) != 1) { - Log::err("Reader", str(format("%s: error reading file: %s") - % path(name) % PHYSFS_getLastError())); + Log::err("Reader", Formatter("%: error reading file: %") + % path(name) % PHYSFS_getLastError()); PHYSFS_close(zf); return false; } @@ -190,7 +186,8 @@ static bool callInitpy(const std::string& archivePath) std::string("__init__.py: ") + (exists ? "found" : "not found")); if (exists) - ASSERT(Reader::runPythonScript("__init__.py")); + // FIXME: Python will cache the __init__ module with no path prefix + ASSERT(Script::create("__init__")); ASSERT(Reader::rmPath(archivePath)); return true; } @@ -209,7 +206,7 @@ bool Reader::init(char* argv0) { ASSERT(PHYSFS_init(argv0) != 0); - // If any of our archives contain a file called "init.py", call it. + // If any of our archives contain a file called "__init__.py", call it. for (Conf::StringVector::const_iterator it = conf.dataPath.begin(); it != conf.dataPath.end(); it++) { const std::string archive = *it; ASSERT(callInitpy(archive)); @@ -238,42 +235,38 @@ void Reader::deinit() bool Reader::prependPath(const std::string& path) { - using namespace boost; - int err = PHYSFS_mount(path.c_str(), NULL, 0); if (err == 0) { - Log::fatal("Reader", str( - format("%s: could not open archive: %s") - % path % PHYSFS_getLastError())); + Log::fatal("Reader", Formatter("%: could not open archive: %") + % path % PHYSFS_getLastError()); return false; } + pythonPrependPath(path); + return true; } bool Reader::appendPath(const std::string& path) { - using namespace boost; - int err = PHYSFS_mount(path.c_str(), NULL, 1); if (err == 0) { - Log::fatal("Reader", str( - format("%s: could not open archive: %s") - % path % PHYSFS_getLastError())); + Log::fatal("Reader", Formatter("%: could not open archive: %") + % path % PHYSFS_getLastError()); return false; } + pythonRmPath(path); + return true; } bool Reader::rmPath(const std::string& path) { - using namespace boost; - int err = PHYSFS_removeFromSearchPath(path.c_str()); if (err == 0) { - Log::err("Reader", str(format("libphysfs: %s: %s") - % path % PHYSFS_getLastError())); + Log::err("Reader", Formatter("libphysfs: %: %") + % path % PHYSFS_getLastError()); return false; } @@ -384,37 +377,6 @@ XMLRef Reader::getXMLDoc(const std::string& name, return result; } -BytecodeRef Reader::getBytecode(const std::string& path) -{ - BytecodeRef existing = bytecodes.momentaryRequest(path); - if (existing) - return existing; - - BytecodeRef result; - if (fileExists(path)) { - std::string code = readString(path); - Bytecode* script = new Bytecode(path, code); - result.reset(script); - } - - bytecodes.momentaryPut(path, result); - return result; -} - -bool Reader::runPythonScript(const std::string& path) -{ - BytecodeRef bc = getBytecode(path); - if (bc && bc->valid()) { - bc->execute(); - return true; - } - else { - Log::err("Script", - bc->filename() + ": Attempt to run broken Python script"); - return false; - } -} - std::string Reader::getText(const std::string& name) { StringRef existing = texts.momentaryRequest(name); diff --git a/src/reader.h b/src/reader.h index 327ead9..c6459d4 100644 --- a/src/reader.h +++ b/src/reader.h @@ -32,7 +32,6 @@ #include #include -#include "bytecode.h" #include "cache.h" #include "image.h" #include "sound.h" @@ -90,11 +89,6 @@ public: static XMLRef getXMLDoc(const std::string& name, const std::string& dtdPath); - static BytecodeRef getBytecode(const std::string& path); - - //! Request a Python script from the World be run. - static bool runPythonScript(const std::string& name); - //! Request a text file from the World. static std::string getText(const std::string& name); diff --git a/src/script-python.cpp b/src/script-python.cpp new file mode 100644 index 0000000..9243ac4 --- /dev/null +++ b/src/script-python.cpp @@ -0,0 +1,293 @@ +/*************************************** +** Tsunagari Tile Engine ** +** script-python.cpp ** +** Copyright 2011-2013 PariahSoft LLC ** +***************************************/ + +// ********** +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// ********** + +#include + +#include "log.h" +#include "python.h" +#include "reader.h" +#include "script-python.h" + + +template<> +ScriptRef Script::create(std::string source) +{ + std::string s_file, s_func; + size_t colon; + PyObject* module = NULL; + PythonScript* script; + + colon = source.find(':'); + if (colon != std::string::npos) { + s_file = source.substr(0, colon); + s_func = source.substr(colon + 1); + } + else { + s_file = source; + } + + if ((module = PyImport_ImportModule(s_file.c_str())) == NULL) { + Log::err("Script", s_file + ": import failed"); + goto err; + } + + script = new PythonScript; + script->module = module; + script->function = s_func; + return ScriptRef(script); + +err: + pythonErr(); + + Py_XDECREF(module); + return ScriptRef(); +} + + +template<> +ScriptRef Script::create(const char* source) +{ + return Script::create(std::string(source)); +} + + +PythonScript::PythonScript() + : module(NULL) +{ +} + + +PythonScript::~PythonScript() +{ + Py_DECREF(module); +} + + +bool PythonScript::validate() +{ + // Always valid...? + return true; +} + + +bool PythonScript::invoke() +{ + assert(function.size()); + + PyObject* result = NULL; + + result = PyObject_CallMethod(module, (char*)function.c_str(), (char*)"()"); + if (result == NULL) { + pythonErr(); + return false; + } + Py_DECREF(result); + return true; +} + + +/* The code below has support for being bound from inside of Python + + +ScriptInst::ScriptInst(const std::string& source) +: dataType(BYTECODE_REF), data(Reader::getBytecode(source)) +{ + if (!validate()) { + Log::err("ScriptInst", "Error loading " + source); + } +} + + +ScriptInst::ScriptInst(boost::python::object& callable) +: dataType(BOOST_PY_OBJ), data(callable) +{ +} + + +ScriptInst::ScriptInst(const ScriptInst& s) +: dataType(s.dataType) +{ + switch (dataType) { + case BYTECODE_REF: + data = s.data.bcr; + break; + case BOOST_PY_OBJ: + data = s.data.bpo; + break; + } +} + + +ScriptInst::~ScriptInst() +{ + switch (dataType) { + case BYTECODE_REF: + data = s.data.bcr; + break; + case BOOST_PY_OBJ: + data = s.data.bpo; + break; + } +} + + +bool ScriptInst::validate() +{ + BytecodeRef& bc = *(BytecodeRef*)data; + + switch (dataType) { + case BYTECODE_REF: + if (!bc) { + Log::err("ScriptInst", ": script not valid"); + return false; + } + if (!bc->valid()) { + Log::err("ScriptInst", bc->filename() + + ": script not valid"); + return false; + } + return true; + case BOOST_PY_OBJ: + return true; + default: + Log::fatal("ScriptInstInternal", "validate(): unknown data type"); + return false; + } +} + + +bool ScriptInst::invoke() +{ + BytecodeRef& bc = *(BytecodeRef*)data; + boost::python::object& callable = *(boost::python::object*)data; + + switch (dataType) { + case BYTECODE_REF: + return (bc && bc->valid()) ? bc->execute() : false; + case BOOST_PY_OBJ: + try { + inPythonScript++; + callable(); + inPythonScript--; + return true; + } catch (boost::python::error_already_set) { + inPythonScript--; + // XXX: How does this interact with a C++/Python callstack? + //Log::err("Python", "Originating from " + source + ":"); + pythonErr(); + return false; + } + default: + Log::fatal("ScriptInstInternal", "invoke(): unknown data type"); + return false; + } +} + + +struct scriptinst_to_python +{ + static PyObject* convert(ScriptInst script) + { + BytecodeRef& bc = *(BytecodeRef*)data; + boost::python::object& callable = *(boost::python::object*)data; + + switch (dataType) { + case BYTECODE_REF: + boost::python::object str; + if (bc) + str = boost::python::object(bc->filename()); + else + str = boost::python::object(""); + return boost::python::incref(str.ptr()); + case BOOST_PY_OBJ: + return boost::python::incref(callable.ptr()); + default: + Log::fatal("ScriptInstInternal", "to_python: convert(): unknown data type"); + return false; + } + } +}; + + +struct scriptinst_from_python +{ + scriptinst_from_python() + { + boost::python::converter::registry::push_back( + &convertible, + &construct, + boost::python::type_id()); + } + + // Can this be converted to a ScriptInst? + static void* convertible(PyObject* obj) + { + //bool callable = obj->ob_type->tp_call != NULL; + //const char* tp_name = obj->ob_type->tp_name; + // XXX: Return non-NULL only if string or + // callable (fn or lambda?). + return obj; + } + + // Convert. boost::python provides us with a chunch of memory that we + // have to construct in-place. + static void construct( + PyObject* obj, + boost::python::converter::rvalue_from_python_stage1_data* data) + { + // Prevent compilation name collisions with "object" by making + // it "bp::object". + namespace bp = boost::python; + + void* storage = + ((bp::converter::rvalue_from_python_storage*)data) + ->storage.bytes; + + if (PyString_Check(obj)) { + const char* value = PyString_AsString(obj); + new (storage) ScriptInst(value); + } + else { + // By default, the PyObject is a borrowed reference, + // which means it hasn't been incref'd. + bp::handle<> hndl(bp::borrowed(obj)); + new (storage) ScriptInst(bp::object(hndl)); + } + + data->convertible = storage; + } +}; + + +void exportScriptInst() +{ + using namespace boost::python; + + to_python_converter(); + scriptinst_from_python(); +} + +*/ \ No newline at end of file diff --git a/src/tiledimage-impl.h b/src/script-python.h similarity index 77% copy from src/tiledimage-impl.h copy to src/script-python.h index c0de97c..4732c75 100644 --- a/src/tiledimage-impl.h +++ b/src/script-python.h @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** tiledimage-impl.h ** +** script-python.h ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ @@ -24,25 +24,27 @@ // IN THE SOFTWARE. // ********** -#ifndef TILEDIMAGE_IMPL_H -#define TILEDIMAGE_IMPL_H +#ifndef SCRIPT_PYTHON_H +#define SCRIPT_PYTHON_H -#include +#include -#include "tiledimage.h" +#include "script.h" -class TiledImageImpl : public TiledImage +class PythonScript : public Script { public: - bool init(void* data, size_t length, unsigned tileW, unsigned tileH); + PythonScript(); + ~PythonScript(); - size_t size() const; - - ImageRef& operator[](size_t n); - const ImageRef& operator[](size_t n) const; + bool validate(); + bool invoke(); private: - std::vector vec; + PyObject* module; + std::string function; + + friend ScriptRef Script::create<>(std::string source); }; #endif diff --git a/src/image.cpp b/src/script.cpp similarity index 90% copy from src/image.cpp copy to src/script.cpp index 1bdc253..9c4a0d3 100644 --- a/src/image.cpp +++ b/src/script.cpp @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** image.cpp ** +** script.cpp ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ @@ -24,7 +24,12 @@ // IN THE SOFTWARE. // ********** -#include "image.h" +#include "script.h" -Image::Image() { } -Image::~Image() { } +Script::Script() { } +Script::~Script() { } + + +void exportScript() +{ +} \ No newline at end of file diff --git a/src/tiledimage-impl.h b/src/script.h similarity index 75% rename from src/tiledimage-impl.h rename to src/script.h index c0de97c..e1dcdd2 100644 --- a/src/tiledimage-impl.h +++ b/src/script.h @@ -1,6 +1,6 @@ /*************************************** ** Tsunagari Tile Engine ** -** tiledimage-impl.h ** +** script.h ** ** Copyright 2011-2013 PariahSoft LLC ** ***************************************/ @@ -24,26 +24,30 @@ // IN THE SOFTWARE. // ********** -#ifndef TILEDIMAGE_IMPL_H -#define TILEDIMAGE_IMPL_H +#ifndef SCRIPT_H +#define SCRIPT_H -#include +#include -#include "tiledimage.h" +class Script; +typedef boost::shared_ptr