diff options
Diffstat (limited to 'engines/sword25')
201 files changed, 48073 insertions, 0 deletions
diff --git a/engines/sword25/detection.cpp b/engines/sword25/detection.cpp new file mode 100644 index 0000000000..3900df2fcf --- /dev/null +++ b/engines/sword25/detection.cpp @@ -0,0 +1,160 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "base/plugins.h" +#include "common/savefile.h" +#include "common/system.h" +#include "engines/advancedDetector.h" + +#include "sword25/sword25.h" +#include "sword25/kernel/persistenceservice.h" + +namespace Sword25 { +uint32 Sword25Engine::getGameFlags() const { return _gameDescription->flags; } +} + +static const PlainGameDescriptor Sword25Game[] = { + {"sword25", "Broken Sword 2.5"}, + {0, 0} +}; + +namespace Sword25 { + +// TODO: Need to decide whether we're going to implement code to detect all the various languages allowed, +// both by the core data package, as well as the extra languages added by the patch file; also, I don't +// think that all the languages supported by the game currently have constants in ScummVM +static const ADGameDescription gameDescriptions[] = { + { + "sword25", + "", + AD_ENTRY1s("data.b25c", "f8b6e03ada2d2f6cf27fbc11ad1572e9", 654310588), + Common::EN_ANY, + Common::kPlatformUnknown, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + { + "sword25", + "Extracted", + {{"_includes.lua", 0, 0, -1}, + {"boot.lua", 0, 0, -1}, + {"kernel.lua", 0, 0, -1}, + AD_LISTEND}, + Common::EN_ANY, + Common::kPlatformUnknown, + GF_EXTRACTED, + Common::GUIO_NONE + }, + AD_TABLE_END_MARKER +}; + +} // End of namespace Sword25 + +static const char *directoryGlobs[] = { + "system", // Used by extracted dats + 0 +}; + +static const ADParams detectionParams = { + // Pointer to ADGameDescription or its superset structure + (const byte *)Sword25::gameDescriptions, + // Size of that superset structure + sizeof(ADGameDescription), + // Number of bytes to compute MD5 sum for + 5000, + // List of all engine targets + Sword25Game, + // Structure for autoupgrading obsolete targets + 0, + // Name of single gameid (optional) + NULL, + // List of files for file-based fallback detection (optional) + 0, + // Flags + 0, + // Additional GUI options (for every game} + Common::GUIO_NOMIDI, + // Maximum directory depth + 2, + // List of directory globs + directoryGlobs +}; + +class Sword25MetaEngine : public AdvancedMetaEngine { +public: + Sword25MetaEngine() : AdvancedMetaEngine(detectionParams) {} + + virtual const char *getName() const { + return "The Broken Sword 2.5 Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "Broken Sword 2.5 (C) Malte Thiesen, Daniel Queteschiner and Michael Elsdorfer"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual bool hasFeature(MetaEngineFeature f) const; + virtual int getMaximumSaveSlot() const { return Sword25::PersistenceService::getSlotCount(); } + virtual SaveStateList listSaves(const char *target) const; +}; + +bool Sword25MetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + if (desc) { + *engine = new Sword25::Sword25Engine(syst, desc); + } + return desc != 0; +} + +bool Sword25MetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves); +} + +SaveStateList Sword25MetaEngine::listSaves(const char *target) const { + Common::String pattern = target; + pattern = pattern + ".???"; + SaveStateList saveList; + + Sword25::PersistenceService ps; + Sword25::setGameTarget(target); + + ps.reloadSlots(); + + for (uint i = 0; i < ps.getSlotCount(); ++i) { + if (ps.isSlotOccupied(i)) { + Common::String desc = ps.getSavegameDescription(i); + saveList.push_back(SaveStateDescriptor(i, desc)); + } + } + + return saveList; +} + +#if PLUGIN_ENABLED_DYNAMIC(SWORD25) + REGISTER_PLUGIN_DYNAMIC(SWORD25, PLUGIN_TYPE_ENGINE, Sword25MetaEngine); +#else + REGISTER_PLUGIN_STATIC(SWORD25, PLUGIN_TYPE_ENGINE, Sword25MetaEngine); +#endif + diff --git a/engines/sword25/fmv/movieplayer.cpp b/engines/sword25/fmv/movieplayer.cpp new file mode 100644 index 0000000000..4193e02b2e --- /dev/null +++ b/engines/sword25/fmv/movieplayer.cpp @@ -0,0 +1,228 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/fmv/movieplayer.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/gfx/panel.h" +#include "sword25/kernel/kernel.h" +#include "sword25/package/packagemanager.h" +#include "sword25/sfx/soundengine.h" + +#define INDIRECTRENDERING 1 + +namespace Sword25 { + +#define BS_LOG_PREFIX "MOVIEPLAYER" + +#define FLT_EPSILON 1.192092896e-07F /* smallest such that 1.0+FLT_EPSILON != 1.0 */ + +#ifdef USE_THEORADEC +MoviePlayer::MoviePlayer(Kernel *pKernel) : Service(pKernel), _decoder(g_system->getMixer()) { + if (!registerScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); +} + +MoviePlayer::~MoviePlayer() { + _decoder.close(); +} + +bool MoviePlayer::loadMovie(const Common::String &filename, uint z) { + // Get the file and load it into the decoder + Common::SeekableReadStream *in = Kernel::getInstance()->getPackage()->getStream(filename); + _decoder.load(in); + + // Ausgabebitmap erstellen + GraphicEngine *pGfx = Kernel::getInstance()->getGfx(); + +#if INDIRECTRENDERING + _outputBitmap = pGfx->getMainPanel()->addDynamicBitmap(_decoder.getWidth(), _decoder.getHeight()); + if (!_outputBitmap.isValid()) { + BS_LOG_ERRORLN("Output bitmap for movie playback could not be created."); + return false; + } + + // Skalierung des Ausgabebitmaps berechnen, so dass es möglichst viel Bildschirmfläche einnimmt. + float screenToVideoWidth = (float)pGfx->getDisplayWidth() / (float)_outputBitmap->getWidth(); + float screenToVideoHeight = (float)pGfx->getDisplayHeight() / (float)_outputBitmap->getHeight(); + float scaleFactor = MIN(screenToVideoWidth, screenToVideoHeight); + + if (abs((int)(scaleFactor - 1.0f)) < FLT_EPSILON) + scaleFactor = 1.0f; + + _outputBitmap->setScaleFactor(scaleFactor); + + // Z-Wert setzen + _outputBitmap->setZ(z); + + // Ausgabebitmap auf dem Bildschirm zentrieren + _outputBitmap->setX((pGfx->getDisplayWidth() - _outputBitmap->getWidth()) / 2); + _outputBitmap->setY((pGfx->getDisplayHeight() - _outputBitmap->getHeight()) / 2); +#else + _backSurface = pGfx->getSurface(); + + _outX = (pGfx->getDisplayWidth() - _decoder.getWidth()) / 2; + _outY = (pGfx->getDisplayHeight() - _decoder.getHeight()) / 2; + + if (_outX < 0) + _outX = 0; + if (_outY < 0) + _outY = 0; +#endif + + return true; +} + +bool MoviePlayer::unloadMovie() { + _decoder.close(); + _outputBitmap.erase(); + + return true; +} + +bool MoviePlayer::play() { + _decoder.pauseVideo(false); + return true; +} + +bool MoviePlayer::pause() { + _decoder.pauseVideo(true); + return true; +} + +void MoviePlayer::update() { + if (_decoder.isVideoLoaded()) { + Graphics::Surface *s = _decoder.decodeNextFrame(); + if (s) { + // Transfer the next frame + assert(s->bytesPerPixel == 4); + +#if INDIRECTRENDERING + byte *frameData = (byte *)s->getBasePtr(0, 0); + _outputBitmap->setContent(frameData, s->pitch * s->h, 0, s->pitch); +#else + g_system->copyRectToScreen((byte *)s->getBasePtr(0, 0), s->pitch, _outX, _outY, MIN(s->w, _backSurface->w), MIN(s->h, _backSurface->h)); + g_system->updateScreen(); +#endif + } else { + // Movie complete, so unload the movie + unloadMovie(); + } + } +} + +bool MoviePlayer::isMovieLoaded() { + return _decoder.isVideoLoaded(); +} + +bool MoviePlayer::isPaused() { + return _decoder.isPaused(); +} + +float MoviePlayer::getScaleFactor() { + if (_decoder.isVideoLoaded()) + return _outputBitmap->getScaleFactorX(); + else + return 0; +} + +void MoviePlayer::setScaleFactor(float scaleFactor) { + if (_decoder.isVideoLoaded()) { + _outputBitmap->setScaleFactor(scaleFactor); + + // Ausgabebitmap auf dem Bildschirm zentrieren + GraphicEngine *gfxPtr = Kernel::getInstance()->getGfx(); + _outputBitmap->setX((gfxPtr->getDisplayWidth() - _outputBitmap->getWidth()) / 2); + _outputBitmap->setY((gfxPtr->getDisplayHeight() - _outputBitmap->getHeight()) / 2); + } +} + +double MoviePlayer::getTime() { + return _decoder.getElapsedTime() / 1000.0; +} + +#else // USE_THEORADEC + +MoviePlayer::MoviePlayer(Kernel *pKernel) : Service(pKernel) { + if (!registerScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); +} + +MoviePlayer::~MoviePlayer() { +} + +bool MoviePlayer::loadMovie(const Common::String &Filename, unsigned int Z) { + return true; +} + +bool MoviePlayer::unloadMovie() { + return true; +} + +bool MoviePlayer::play() { + return true; +} + +bool MoviePlayer::pause() { + return true; +} + +void MoviePlayer::update() { +} + +bool MoviePlayer::isMovieLoaded() { + return true; +} + +bool MoviePlayer::isPaused() { + return true; +} + +float MoviePlayer::getScaleFactor() { + return 1.0f; +} + +void MoviePlayer::setScaleFactor(float ScaleFactor) { +} + +double MoviePlayer::getTime() { + return 1.0; +} + +#endif // USE_THEORADEC + +} // End of namespace Sword25 diff --git a/engines/sword25/fmv/movieplayer.h b/engines/sword25/fmv/movieplayer.h new file mode 100644 index 0000000000..350407cea5 --- /dev/null +++ b/engines/sword25/fmv/movieplayer.h @@ -0,0 +1,156 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_MOVIEPLAYER_H +#define SWORD25_MOVIEPLAYER_H + +#include "common/scummsys.h" // for USE_THEORADEC + +#include "sword25/kernel/common.h" +#include "sword25/kernel/service.h" +#include "sword25/gfx/bitmap.h" + +#ifdef USE_THEORADEC +#include "sword25/fmv/theora_decoder.h" +#endif + +namespace Sword25 { + +class MoviePlayer : public Service { +public: + // ----------------------------------------------------------------------------- + // Constructor / Destructor + // ----------------------------------------------------------------------------- + + MoviePlayer(Kernel *pKernel); + ~MoviePlayer(); + + // ----------------------------------------------------------------------------- + // Player interface must be implemented by a Movie Player + // ----------------------------------------------------------------------------- + + /** + * Loads a movie file + * + * This method loads a movie file and prepares it for playback. + * There can be oly one movie file loaded at a time. If you already have loaded a + * movie file, it will be unloaded and, if necessary, stopped playing. + * @param Filename The filename of the movie file to be loaded + * @param Z Z indicates the position of the film on the main graphics layer + * @return Returns false if an error occured while loading, otherwise true. + */ + bool loadMovie(const Common::String &filename, uint z); + + /** + * Unloads the currently loaded movie file. + * @return Returns false if an error occurred while unloading, otherwise true. + * @remark This method can only be called when IsMovieLoaded() returns true. + */ + bool unloadMovie(); + + /** + * Plays the loaded movie. + * + * The film will be keeping the aspect ratio of the screen. + * If the film was previously paused with Pause(), then the film will resume playing. + * @return Returns false if an error occurred while starting, otherwise true. + * @remark This method can only be called when IsMovieLoaded() returns true. + */ + bool play(); + + /** + * Pauses movie playback. + * + * A paused movie can later be resumed by calling the Play() method again. + * @return Returns false if an error occurred while pausing, otherwise true. + * @remark This method can only be called when IsMovieLoaded() returns true. + */ + bool pause(); + + /** + * This function must be called once per frame. + */ + void update(); + + /** + * Returns whether a film is loaded for playback. + */ + bool isMovieLoaded(); + + /** + * Returns whether the movie playback is paused. + * @remark This method can only be called when IsMovieLoaded() returns true. + */ + bool isPaused(); + + /** + * Returns the scaling factor for the loaded film. + * + * When a movie is loaded, the scaling factor is automatically selected so that the film + * takes the maximum screen space, without the film being distorted. + * @return Returns the scaling factor of the film. + * @remark This method can only be called when IsMovieLoaded() returns true. + */ + float getScaleFactor(); + + /** + * Sets the factor by which the loaded film is to be scaled. + * @param ScaleFactor The desired scale factor. + * @remark This method can only be called when IsMovieLoaded() returns true. + */ + void setScaleFactor(float scaleFactor); + + /** + * Returns the current playing position in seconds. + * @remark This method can only be called when IsMovieLoaded() returns true. + */ + double getTime(); + +private: + bool registerScriptBindings(); + + +#ifdef USE_THEORADEC + TheoraDecoder _decoder; + + Graphics::Surface *_backSurface; + int _outX, _outY; + + RenderObjectPtr<Bitmap> _outputBitmap; +#endif +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/fmv/movieplayer_script.cpp b/engines/sword25/fmv/movieplayer_script.cpp new file mode 100644 index 0000000000..aa854448ff --- /dev/null +++ b/engines/sword25/fmv/movieplayer_script.cpp @@ -0,0 +1,163 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "common/scummsys.h" // for USE_THEORADEC + +#include "sword25/kernel/common.h" +#include "sword25/kernel/kernel.h" +#include "sword25/script/script.h" +#include "sword25/script/luabindhelper.h" + +#include "sword25/fmv/movieplayer.h" + +namespace Sword25 { + +int loadMovie(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + lua_pushbooleancpp(L, FMVPtr->loadMovie(luaL_checkstring(L, 1), lua_gettop(L) == 2 ? static_cast<uint>(luaL_checknumber(L, 2)) : 10)); + + return 1; +} + +int unloadMovie(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + lua_pushbooleancpp(L, FMVPtr->unloadMovie()); + + return 1; +} + +int play(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + lua_pushbooleancpp(L, FMVPtr->play()); + + return 1; +} + +int pause(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + lua_pushbooleancpp(L, FMVPtr->pause()); + + return 1; +} + +int update(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + FMVPtr->update(); + + return 0; +} + +int isMovieLoaded(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + lua_pushbooleancpp(L, FMVPtr->isMovieLoaded()); + + return 1; +} + +int isPaused(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + lua_pushbooleancpp(L, FMVPtr->isPaused()); + + return 1; +} + +int getScaleFactor(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + lua_pushnumber(L, FMVPtr->getScaleFactor()); + + return 1; +} + +int setScaleFactor(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + FMVPtr->setScaleFactor(static_cast<float>(luaL_checknumber(L, 1))); + + return 0; +} + +int getTime(lua_State *L) { + MoviePlayer *FMVPtr = Kernel::getInstance()->getFMV(); + BS_ASSERT(FMVPtr); + + lua_pushnumber(L, FMVPtr->getTime()); + + return 1; +} + +const char *LIBRARY_NAME = "Movieplayer"; + +const luaL_reg LIBRARY_FUNCTIONS[] = { + { "LoadMovie", loadMovie }, + { "UnloadMovie", unloadMovie }, + { "Play", play }, + { "Pause", pause }, + { "Update", update }, + { "IsMovieLoaded", isMovieLoaded }, + { "IsPaused", isPaused }, + { "GetScaleFactor", getScaleFactor }, + { "SetScaleFactor", setScaleFactor }, + { "GetTime", getTime }, + { 0, 0 } +}; + +bool MoviePlayer::registerScriptBindings() { + ScriptEngine *pScript = Kernel::getInstance()->getScript(); + BS_ASSERT(pScript); + lua_State *L = static_cast<lua_State *>(pScript->getScriptObject()); + BS_ASSERT(L); + + if (!LuaBindhelper::addFunctionsToLib(L, LIBRARY_NAME, LIBRARY_FUNCTIONS)) return false; + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/fmv/theora_decoder.cpp b/engines/sword25/fmv/theora_decoder.cpp new file mode 100644 index 0000000000..d211136614 --- /dev/null +++ b/engines/sword25/fmv/theora_decoder.cpp @@ -0,0 +1,499 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * Source is based on the player example from libvorbis package + * + * THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE. + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. + * + * THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2009 + * by the Xiph.Org Foundation and contributors http://www.xiph.org/ + * + */ + +#include "sword25/fmv/theora_decoder.h" + +#ifdef USE_THEORADEC +#include "sword25/fmv/yuvtorgba.h" +#include "common/system.h" +#include "sound/decoders/raw.h" + +namespace Sword25 { + +#define AUDIOFD_FRAGSIZE 10240 + +static double rint(double v) { + return floor(v + 0.5); +} + +TheoraDecoder::TheoraDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : _mixer(mixer) { + _fileStream = 0; + _surface = 0; + + _theoraPacket = 0; + _vorbisPacket = 0; + _theoraDecode = 0; + _theoraSetup = 0; + _stateFlag = false; + + _soundType = soundType; + _audStream = 0; + _audHandle = new Audio::SoundHandle(); + + ogg_sync_init(&_oggSync); + + _curFrame = 0; + _audiobuf = (ogg_int16_t *)calloc(AUDIOFD_FRAGSIZE, sizeof(ogg_int16_t)); + + reset(); +} + +TheoraDecoder::~TheoraDecoder() { + close(); + delete _fileStream; + delete _audHandle; + free(_audiobuf); +} + +void TheoraDecoder::queuePage(ogg_page *page) { + if (_theoraPacket) + ogg_stream_pagein(&_theoraOut, page); + + if (_vorbisPacket) + ogg_stream_pagein(&_vorbisOut, page); +} + +int TheoraDecoder::bufferData() { + char *buffer = ogg_sync_buffer(&_oggSync, 4096); + int bytes = _fileStream->read(buffer, 4096); + + ogg_sync_wrote(&_oggSync, bytes); + + return bytes; +} + +bool TheoraDecoder::load(Common::SeekableReadStream *stream) { + close(); + + _fileStream = stream; + + // start up Ogg stream synchronization layer + ogg_sync_init(&_oggSync); + + // init supporting Vorbis structures needed in header parsing + vorbis_info_init(&_vorbisInfo); + vorbis_comment_init(&_vorbisComment); + + // init supporting Theora structures needed in header parsing + th_comment_init(&_theoraComment); + th_info_init(&_theoraInfo); + + // Ogg file open; parse the headers + // Only interested in Vorbis/Theora streams + while (!_stateFlag) { + int ret = bufferData(); + + if (ret == 0) + break; + + while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { + ogg_stream_state test; + + // is this a mandated initial header? If not, stop parsing + if (!ogg_page_bos(&_oggPage)) { + // don't leak the page; get it into the appropriate stream + queuePage(&_oggPage); + _stateFlag = true; + break; + } + + ogg_stream_init(&test, ogg_page_serialno(&_oggPage)); + ogg_stream_pagein(&test, &_oggPage); + ogg_stream_packetout(&test, &_oggPacket); + + // identify the codec: try theora + if (!_theoraPacket && th_decode_headerin(&_theoraInfo, &_theoraComment, &_theoraSetup, &_oggPacket) >= 0) { + // it is theora + memcpy(&_theoraOut, &test, sizeof(test)); + _theoraPacket = 1; + } else if (!_vorbisPacket && vorbis_synthesis_headerin(&_vorbisInfo, &_vorbisComment, &_oggPacket) >= 0) { + // it is vorbis + memcpy(&_vorbisOut, &test, sizeof(test)); + _vorbisPacket = 1; + } else { + // whatever it is, we don't care about it + ogg_stream_clear(&test); + } + } + // fall through to non-bos page parsing + } + + // we're expecting more header packets. + while ((_theoraPacket && _theoraPacket < 3) || (_vorbisPacket && _vorbisPacket < 3)) { + int ret; + + // look for further theora headers + while (_theoraPacket && (_theoraPacket < 3) && (ret = ogg_stream_packetout(&_theoraOut, &_oggPacket))) { + if (ret < 0) + error("Error parsing Theora stream headers; corrupt stream?"); + + if (!th_decode_headerin(&_theoraInfo, &_theoraComment, &_theoraSetup, &_oggPacket)) + error("Error parsing Theora stream headers; corrupt stream?"); + + _theoraPacket++; + } + + // look for more vorbis header packets + while (_vorbisPacket && (_vorbisPacket < 3) && (ret = ogg_stream_packetout(&_vorbisOut, &_oggPacket))) { + if (ret < 0) + error("Error parsing Vorbis stream headers; corrupt stream?"); + + if (vorbis_synthesis_headerin(&_vorbisInfo, &_vorbisComment, &_oggPacket)) + error("Error parsing Vorbis stream headers; corrupt stream?"); + + _vorbisPacket++; + + if (_vorbisPacket == 3) + break; + } + + // The header pages/packets will arrive before anything else we + // care about, or the stream is not obeying spec + + if (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { + queuePage(&_oggPage); // demux into the appropriate stream + } else { + ret = bufferData(); // someone needs more data + + if (ret == 0) + error("End of file while searching for codec headers."); + } + } + + // and now we have it all. initialize decoders + if (_theoraPacket) { + _theoraDecode = th_decode_alloc(&_theoraInfo, _theoraSetup); + debugN(1, "Ogg logical stream %lx is Theora %dx%d %.02f fps", + _theoraOut.serialno, _theoraInfo.pic_width, _theoraInfo.pic_height, + (double)_theoraInfo.fps_numerator / _theoraInfo.fps_denominator); + + switch (_theoraInfo.pixel_fmt) { + case TH_PF_420: + debug(1, " 4:2:0 video"); + break; + case TH_PF_422: + debug(1, " 4:2:2 video"); + break; + case TH_PF_444: + debug(1, " 4:4:4 video"); + break; + case TH_PF_RSVD: + default: + debug(1, " video\n (UNKNOWN Chroma sampling!)"); + break; + } + + if (_theoraInfo.pic_width != _theoraInfo.frame_width || _theoraInfo.pic_height != _theoraInfo.frame_height) + debug(1, " Frame content is %dx%d with offset (%d,%d).", + _theoraInfo.frame_width, _theoraInfo.frame_height, _theoraInfo.pic_x, _theoraInfo.pic_y); + + switch (_theoraInfo.colorspace){ + case TH_CS_UNSPECIFIED: + /* nothing to report */ + break;; + case TH_CS_ITU_REC_470M: + debug(1, " encoder specified ITU Rec 470M (NTSC) color."); + break; + case TH_CS_ITU_REC_470BG: + debug(1, " encoder specified ITU Rec 470BG (PAL) color."); + break; + default: + debug(1, "warning: encoder specified unknown colorspace (%d).", _theoraInfo.colorspace); + break; + } + + debug(1, "Encoded by %s", _theoraComment.vendor); + if (_theoraComment.comments) { + debug(1, "theora comment header:"); + for (int i = 0; i < _theoraComment.comments; i++) { + if (_theoraComment.user_comments[i]) { + int len = _theoraComment.comment_lengths[i]; + char *value = (char *)malloc(len + 1); + memcpy(value, _theoraComment.user_comments[i], len); + value[len] = '\0'; + debug(1, "\t%s", value); + free(value); + } + } + } + + th_decode_ctl(_theoraDecode, TH_DECCTL_GET_PPLEVEL_MAX, &_ppLevelMax, sizeof(_ppLevelMax)); + _ppLevel = _ppLevelMax; + th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &_ppLevel, sizeof(_ppLevel)); + _ppInc = 0; + } else { + // tear down the partial theora setup + th_info_clear(&_theoraInfo); + th_comment_clear(&_theoraComment); + } + + th_setup_free(_theoraSetup); + _theoraSetup = 0; + + if (_vorbisPacket) { + vorbis_synthesis_init(&_vorbisDSP, &_vorbisInfo); + vorbis_block_init(&_vorbisDSP, &_vorbisBlock); + debug(3, "Ogg logical stream %lx is Vorbis %d channel %ld Hz audio.", + _vorbisOut.serialno, _vorbisInfo.channels, _vorbisInfo.rate); + } else { + // tear down the partial vorbis setup + vorbis_info_clear(&_vorbisInfo); + vorbis_comment_clear(&_vorbisComment); + } + + // open audio + if (_vorbisPacket) { + _audStream = createAudioStream(); + if (_audStream && _mixer) + _mixer->playStream(_soundType, _audHandle, _audStream); + } + + _surface = new Graphics::Surface(); + + _surface->create(_theoraInfo.frame_width, _theoraInfo.frame_height, 4); + + return true; +} + +void TheoraDecoder::close() { + if (_vorbisPacket) { + ogg_stream_clear(&_vorbisOut); + vorbis_block_clear(&_vorbisBlock); + vorbis_dsp_clear(&_vorbisDSP); + vorbis_comment_clear(&_vorbisComment); + vorbis_info_clear(&_vorbisInfo); + + if (_mixer) + _mixer->stopHandle(*_audHandle); + _audStream = 0; + _vorbisPacket = false; + } + if (_theoraPacket) { + ogg_stream_clear(&_theoraOut); + th_decode_free(_theoraDecode); + th_comment_clear(&_theoraComment); + th_info_clear(&_theoraInfo); + _theoraDecode = 0; + _theoraPacket = false; + } + + if (!_fileStream) + return; + + ogg_sync_clear(&_oggSync); + + delete _fileStream; + _fileStream = 0; + + _surface->free(); + delete _surface; + _surface = 0; + + reset(); +} + +Graphics::Surface *TheoraDecoder::decodeNextFrame() { + int i, j; + +// _stateFlag = false; // playback has not begun + + // we want a video and audio frame ready to go at all times. If + // we have to buffer incoming, buffer the compressed data (ie, let + // ogg do the buffering) + while (_vorbisPacket && !_audiobufReady) { + int ret; + float **pcm; + + // if there's pending, decoded audio, grab it + if ((ret = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm)) > 0) { + int count = _audiobufFill / 2; + int maxsamples = (AUDIOFD_FRAGSIZE - _audiobufFill) / 2 / _vorbisInfo.channels; + for (i = 0; i < ret && i < maxsamples; i++) + for (j = 0; j < _vorbisInfo.channels; j++) { + int val = CLIP((int)rint(pcm[j][i] * 32767.f), -32768, 32767); + _audiobuf[count++] = val; + } + + vorbis_synthesis_read(&_vorbisDSP, i); + _audiobufFill += i * _vorbisInfo.channels * 2; + + if (_audiobufFill == AUDIOFD_FRAGSIZE) + _audiobufReady = true; + + if (_vorbisDSP.granulepos >= 0) + _audiobufGranulePos = _vorbisDSP.granulepos - ret + i; + else + _audiobufGranulePos += i; + } else { + + // no pending audio; is there a pending packet to decode? + if (ogg_stream_packetout(&_vorbisOut, &_oggPacket) > 0) { + if (vorbis_synthesis(&_vorbisBlock, &_oggPacket) == 0) // test for success! + vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock); + } else // we need more data; break out to suck in another page + break; + } + } + + while (_theoraPacket && !_videobufReady) { + // theora is one in, one out... + if (ogg_stream_packetout(&_theoraOut, &_oggPacket) > 0) { + + if (_ppInc) { + _ppLevel += _ppInc; + th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &_ppLevel, sizeof(_ppLevel)); + _ppInc = 0; + } + // HACK: This should be set after a seek or a gap, but we might not have + // a granulepos for the first packet (we only have them for the last + // packet on a page), so we just set it as often as we get it. + // To do this right, we should back-track from the last packet on the + // page and compute the correct granulepos for the first packet after + // a seek or a gap. + if (_oggPacket.granulepos >= 0) { + th_decode_ctl(_theoraDecode, TH_DECCTL_SET_GRANPOS, &_oggPacket.granulepos, sizeof(_oggPacket.granulepos)); + } + if (th_decode_packetin(_theoraDecode, &_oggPacket, &_videobufGranulePos) == 0) { + _videobufTime = th_granule_time(_theoraDecode, _videobufGranulePos); + _curFrame++; + + _videobufReady = true; + } + } else + break; + } + + if (!_videobufReady && !_audiobufReady && _fileStream->eos()) { + return NULL; + } + + if (!_videobufReady || !_audiobufReady) { + // no data yet for somebody. Grab another page + bufferData(); + while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) { + queuePage(&_oggPage); + } + } + + // If playback has begun, top audio buffer off immediately. + if (_stateFlag && _audiobufReady) { + _audStream->queueBuffer((byte *)_audiobuf, AUDIOFD_FRAGSIZE, DisposeAfterUse::NO, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN | Audio::FLAG_STEREO); + + // The audio mixer is now responsible for the old audio buffer. + // We need to create a new one. + _audiobuf = (ogg_int16_t *)calloc(AUDIOFD_FRAGSIZE, sizeof(ogg_int16_t)); + _audiobufFill = 0; + _audiobufReady = false; + } + + // are we at or past time for this video frame? + if (_stateFlag && _videobufReady) { + th_ycbcr_buffer yuv; + + th_decode_ycbcr_out(_theoraDecode, yuv); + + // Convert YUV data to RGB data + YUVtoBGRA::translate(yuv, _theoraInfo, (byte *)_surface->getBasePtr(0, 0), _surface->pitch * _surface->h); + + switch (_theoraInfo.pixel_fmt) { + case TH_PF_420: + break; + case TH_PF_422: + break; + case TH_PF_444: + break; + default: + break; + } + + _videobufReady = false; + } + + // if our buffers either don't exist or are ready to go, + // we can begin playback + if ((!_theoraPacket || _videobufReady) && + (!_vorbisPacket || _audiobufReady)) + _stateFlag = true; + + // same if we've run out of input + if (_fileStream->eos()) + _stateFlag = true; + + return _surface; +} + +void TheoraDecoder::reset() { + VideoDecoder::reset(); + + if (_fileStream) + _fileStream->seek(0); + + _videobufReady = false; + _videobufGranulePos = -1; + _videobufTime = 0; + + _audiobufFill = 0; + _audiobufReady = false; + _audiobufGranulePos = 0; + + _curFrame = 0; + + _theoraPacket = 0; + _vorbisPacket = 0; + _stateFlag = false; +} + +bool TheoraDecoder::endOfVideo() const { + return !isVideoLoaded(); +} + + +uint32 TheoraDecoder::getElapsedTime() const { + if (_audStream && _mixer) + return _mixer->getSoundElapsedTime(*_audHandle); + + return VideoDecoder::getElapsedTime(); +} + +Audio::QueuingAudioStream *TheoraDecoder::createAudioStream() { + return Audio::makeQueuingAudioStream(_vorbisInfo.rate, _vorbisInfo.channels); +} + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/fmv/theora_decoder.h b/engines/sword25/fmv/theora_decoder.h new file mode 100644 index 0000000000..f6c622563b --- /dev/null +++ b/engines/sword25/fmv/theora_decoder.h @@ -0,0 +1,158 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SWORD25_THEORADECODER_H +#define SWORD25_THEORADECODER_H + +#include "common/scummsys.h" // for USE_THEORADEC + +#ifdef USE_THEORADEC + +#include "graphics/video/video_decoder.h" +#include "sound/audiostream.h" +#include "sound/mixer.h" + +#include <theora/theoradec.h> +#include <vorbis/codec.h> + +namespace Common { +class SeekableReadStream; +} + +namespace Sword25 { + +/** + * + * Decoder for Theora videos. + * Video decoder used in engines: + * - sword25 + */ +class TheoraDecoder : public Graphics::FixedRateVideoDecoder { +public: + TheoraDecoder(Audio::Mixer *mixer = 0, Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType); + virtual ~TheoraDecoder(); + + /** + * Load a video file + * @param stream the stream to load + */ + bool load(Common::SeekableReadStream *stream); + void close(); + void reset(); + + /** + * Decode the next frame and return the frame's surface + * @note the return surface should *not* be freed + * @note this may return 0, in which case the last frame should be kept on screen + */ + Graphics::Surface *decodeNextFrame(); + + bool isVideoLoaded() const { + return _fileStream != 0; + } + bool isPaused() const { + return (VideoDecoder::isPaused() || !isVideoLoaded()); + } + + uint16 getWidth() const { + return _surface->w; + } + uint16 getHeight() const { + return _surface->h; + } + uint32 getFrameCount() const { + // It is not possible to get frame count easily + // I.e. seeking is required + assert(0); + return 0; + } + Graphics::PixelFormat getPixelFormat() const { + return Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24); + } + + uint32 getElapsedTime() const; + + bool endOfVideo() const; + +protected: + Common::Rational getFrameRate() const { + return _frameRate; + } + +private: + void queuePage(ogg_page *page); + int bufferData(); + Audio::QueuingAudioStream *createAudioStream(); + +private: + Common::SeekableReadStream *_fileStream; + Graphics::Surface *_surface; + Common::Rational _frameRate; + uint32 _frameCount; + + Audio::Mixer *_mixer; + Audio::Mixer::SoundType _soundType; + Audio::SoundHandle *_audHandle; + Audio::QueuingAudioStream *_audStream; + + ogg_sync_state _oggSync; + ogg_page _oggPage; + ogg_packet _oggPacket; + ogg_stream_state _vorbisOut; + ogg_stream_state _theoraOut; + th_info _theoraInfo; + th_comment _theoraComment; + th_dec_ctx *_theoraDecode; + th_setup_info *_theoraSetup; + vorbis_info _vorbisInfo; + vorbis_dsp_state _vorbisDSP; + vorbis_block _vorbisBlock; + vorbis_comment _vorbisComment; + + int _theoraPacket; + int _vorbisPacket; + bool _stateFlag; + + int _ppLevelMax; + int _ppLevel; + int _ppInc; + + // single frame video buffering + bool _videobufReady; + ogg_int64_t _videobufGranulePos; + double _videobufTime; + + // single audio fragment audio buffering + int _audiobufFill; + bool _audiobufReady; + ogg_int16_t *_audiobuf; + ogg_int64_t _audiobufGranulePos; // time position of last sample +}; + +} // End of namespace Sword25 + +#endif + +#endif diff --git a/engines/sword25/fmv/yuvtorgba.cpp b/engines/sword25/fmv/yuvtorgba.cpp new file mode 100644 index 0000000000..e9d0189265 --- /dev/null +++ b/engines/sword25/fmv/yuvtorgba.cpp @@ -0,0 +1,247 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/fmv/yuvtorgba.h" + +#ifdef USE_THEORADEC + +namespace Sword25 { + +static const int PRECISION = 32768; +static const int COEFFS_Y[256] = { + -593888, -555746, -517604, -479462, -441320, -403178, -365036, -326894, -288752, -250610, -212468, -174326, -136184, -98042, -59900, -21758, + 16384, 54526, 92668, 130810, 168952, 207094, 245236, 283378, 321520, 359662, 397804, 435946, 474088, 512230, 550372, 588514, + 626656, 664798, 702940, 741082, 779224, 817366, 855508, 893650, 931792, 969934, 1008076, 1046218, 1084360, 1122502, 1160644, 1198786, + 1236928, 1275070, 1313212, 1351354, 1389496, 1427638, 1465780, 1503922, 1542064, 1580206, 1618348, 1656490, 1694632, 1732774, 1770916, 1809058, + 1847200, 1885342, 1923484, 1961626, 1999768, 2037910, 2076052, 2114194, 2152336, 2190478, 2228620, 2266762, 2304904, 2343046, 2381188, 2419330, + 2457472, 2495614, 2533756, 2571898, 2610040, 2648182, 2686324, 2724466, 2762608, 2800750, 2838892, 2877034, 2915176, 2953318, 2991460, 3029602, + 3067744, 3105886, 3144028, 3182170, 3220312, 3258454, 3296596, 3334738, 3372880, 3411022, 3449164, 3487306, 3525448, 3563590, 3601732, 3639874, + 3678016, 3716158, 3754300, 3792442, 3830584, 3868726, 3906868, 3945010, 3983152, 4021294, 4059436, 4097578, 4135720, 4173862, 4212004, 4250146, + 4288288, 4326430, 4364572, 4402714, 4440856, 4478998, 4517140, 4555282, 4593424, 4631566, 4669708, 4707850, 4745992, 4784134, 4822276, 4860418, + 4898560, 4936702, 4974844, 5012986, 5051128, 5089270, 5127412, 5165554, 5203696, 5241838, 5279980, 5318122, 5356264, 5394406, 5432548, 5470690, + 5508832, 5546974, 5585116, 5623258, 5661400, 5699542, 5737684, 5775826, 5813968, 5852110, 5890252, 5928394, 5966536, 6004678, 6042820, 6080962, + 6119104, 6157246, 6195388, 6233530, 6271672, 6309814, 6347956, 6386098, 6424240, 6462382, 6500524, 6538666, 6576808, 6614950, 6653092, 6691234, + 6729376, 6767518, 6805660, 6843802, 6881944, 6920086, 6958228, 6996370, 7034512, 7072654, 7110796, 7148938, 7187080, 7225222, 7263364, 7301506, + 7339648, 7377790, 7415932, 7454074, 7492216, 7530358, 7568500, 7606642, 7644784, 7682926, 7721068, 7759210, 7797352, 7835494, 7873636, 7911778, + 7949920, 7988062, 8026204, 8064346, 8102488, 8140630, 8178772, 8216914, 8255056, 8293198, 8331340, 8369482, 8407624, 8445766, 8483908, 8522050, + 8560192, 8598334, 8636476, 8674618, 8712760, 8750902, 8789044, 8827186, 8865328, 8903470, 8941612, 8979754, 9017896, 9056038, 9094180, 9132322, +}; +static const int COEFFS_RV[256] = { + -6694144, -6641846, -6589548, -6537250, -6484952, -6432654, -6380356, -6328058, -6275760, -6223462, -6171164, -6118866, -6066568, -6014270, -5961972, -5909674, + -5857376, -5805078, -5752780, -5700482, -5648184, -5595886, -5543588, -5491290, -5438992, -5386694, -5334396, -5282098, -5229800, -5177502, -5125204, -5072906, + -5020608, -4968310, -4916012, -4863714, -4811416, -4759118, -4706820, -4654522, -4602224, -4549926, -4497628, -4445330, -4393032, -4340734, -4288436, -4236138, + -4183840, -4131542, -4079244, -4026946, -3974648, -3922350, -3870052, -3817754, -3765456, -3713158, -3660860, -3608562, -3556264, -3503966, -3451668, -3399370, + -3347072, -3294774, -3242476, -3190178, -3137880, -3085582, -3033284, -2980986, -2928688, -2876390, -2824092, -2771794, -2719496, -2667198, -2614900, -2562602, + -2510304, -2458006, -2405708, -2353410, -2301112, -2248814, -2196516, -2144218, -2091920, -2039622, -1987324, -1935026, -1882728, -1830430, -1778132, -1725834, + -1673536, -1621238, -1568940, -1516642, -1464344, -1412046, -1359748, -1307450, -1255152, -1202854, -1150556, -1098258, -1045960, -993662, -941364, -889066, + -836768, -784470, -732172, -679874, -627576, -575278, -522980, -470682, -418384, -366086, -313788, -261490, -209192, -156894, -104596, -52298, + 0, 52298, 104596, 156894, 209192, 261490, 313788, 366086, 418384, 470682, 522980, 575278, 627576, 679874, 732172, 784470, + 836768, 889066, 941364, 993662, 1045960, 1098258, 1150556, 1202854, 1255152, 1307450, 1359748, 1412046, 1464344, 1516642, 1568940, 1621238, + 1673536, 1725834, 1778132, 1830430, 1882728, 1935026, 1987324, 2039622, 2091920, 2144218, 2196516, 2248814, 2301112, 2353410, 2405708, 2458006, + 2510304, 2562602, 2614900, 2667198, 2719496, 2771794, 2824092, 2876390, 2928688, 2980986, 3033284, 3085582, 3137880, 3190178, 3242476, 3294774, + 3347072, 3399370, 3451668, 3503966, 3556264, 3608562, 3660860, 3713158, 3765456, 3817754, 3870052, 3922350, 3974648, 4026946, 4079244, 4131542, + 4183840, 4236138, 4288436, 4340734, 4393032, 4445330, 4497628, 4549926, 4602224, 4654522, 4706820, 4759118, 4811416, 4863714, 4916012, 4968310, + 5020608, 5072906, 5125204, 5177502, 5229800, 5282098, 5334396, 5386694, 5438992, 5491290, 5543588, 5595886, 5648184, 5700482, 5752780, 5805078, + 5857376, 5909674, 5961972, 6014270, 6066568, 6118866, 6171164, 6223462, 6275760, 6328058, 6380356, 6432654, 6484952, 6537250, 6589548, 6641846, +}; +static const int COEFFS_GU[256] = { + 1639936, 1627124, 1614312, 1601500, 1588688, 1575876, 1563064, 1550252, 1537440, 1524628, 1511816, 1499004, 1486192, 1473380, 1460568, 1447756, + 1434944, 1422132, 1409320, 1396508, 1383696, 1370884, 1358072, 1345260, 1332448, 1319636, 1306824, 1294012, 1281200, 1268388, 1255576, 1242764, + 1229952, 1217140, 1204328, 1191516, 1178704, 1165892, 1153080, 1140268, 1127456, 1114644, 1101832, 1089020, 1076208, 1063396, 1050584, 1037772, + 1024960, 1012148, 999336, 986524, 973712, 960900, 948088, 935276, 922464, 909652, 896840, 884028, 871216, 858404, 845592, 832780, + 819968, 807156, 794344, 781532, 768720, 755908, 743096, 730284, 717472, 704660, 691848, 679036, 666224, 653412, 640600, 627788, + 614976, 602164, 589352, 576540, 563728, 550916, 538104, 525292, 512480, 499668, 486856, 474044, 461232, 448420, 435608, 422796, + 409984, 397172, 384360, 371548, 358736, 345924, 333112, 320300, 307488, 294676, 281864, 269052, 256240, 243428, 230616, 217804, + 204992, 192180, 179368, 166556, 153744, 140932, 128120, 115308, 102496, 89684, 76872, 64060, 51248, 38436, 25624, 12812, + 0, -12812, -25624, -38436, -51248, -64060, -76872, -89684, -102496, -115308, -128120, -140932, -153744, -166556, -179368, -192180, + -204992, -217804, -230616, -243428, -256240, -269052, -281864, -294676, -307488, -320300, -333112, -345924, -358736, -371548, -384360, -397172, + -409984, -422796, -435608, -448420, -461232, -474044, -486856, -499668, -512480, -525292, -538104, -550916, -563728, -576540, -589352, -602164, + -614976, -627788, -640600, -653412, -666224, -679036, -691848, -704660, -717472, -730284, -743096, -755908, -768720, -781532, -794344, -807156, + -819968, -832780, -845592, -858404, -871216, -884028, -896840, -909652, -922464, -935276, -948088, -960900, -973712, -986524, -999336, -1012148, + -1024960, -1037772, -1050584, -1063396, -1076208, -1089020, -1101832, -1114644, -1127456, -1140268, -1153080, -1165892, -1178704, -1191516, -1204328, -1217140, + -1229952, -1242764, -1255576, -1268388, -1281200, -1294012, -1306824, -1319636, -1332448, -1345260, -1358072, -1370884, -1383696, -1396508, -1409320, -1422132, + -1434944, -1447756, -1460568, -1473380, -1486192, -1499004, -1511816, -1524628, -1537440, -1550252, -1563064, -1575876, -1588688, -1601500, -1614312, -1627124, +}; +static const int COEFFS_GV[256] = { + 3409920, 3383280, 3356640, 3330000, 3303360, 3276720, 3250080, 3223440, 3196800, 3170160, 3143520, 3116880, 3090240, 3063600, 3036960, 3010320, + 2983680, 2957040, 2930400, 2903760, 2877120, 2850480, 2823840, 2797200, 2770560, 2743920, 2717280, 2690640, 2664000, 2637360, 2610720, 2584080, + 2557440, 2530800, 2504160, 2477520, 2450880, 2424240, 2397600, 2370960, 2344320, 2317680, 2291040, 2264400, 2237760, 2211120, 2184480, 2157840, + 2131200, 2104560, 2077920, 2051280, 2024640, 1998000, 1971360, 1944720, 1918080, 1891440, 1864800, 1838160, 1811520, 1784880, 1758240, 1731600, + 1704960, 1678320, 1651680, 1625040, 1598400, 1571760, 1545120, 1518480, 1491840, 1465200, 1438560, 1411920, 1385280, 1358640, 1332000, 1305360, + 1278720, 1252080, 1225440, 1198800, 1172160, 1145520, 1118880, 1092240, 1065600, 1038960, 1012320, 985680, 959040, 932400, 905760, 879120, + 852480, 825840, 799200, 772560, 745920, 719280, 692640, 666000, 639360, 612720, 586080, 559440, 532800, 506160, 479520, 452880, + 426240, 399600, 372960, 346320, 319680, 293040, 266400, 239760, 213120, 186480, 159840, 133200, 106560, 79920, 53280, 26640, + 0, -26640, -53280, -79920, -106560, -133200, -159840, -186480, -213120, -239760, -266400, -293040, -319680, -346320, -372960, -399600, + -426240, -452880, -479520, -506160, -532800, -559440, -586080, -612720, -639360, -666000, -692640, -719280, -745920, -772560, -799200, -825840, + -852480, -879120, -905760, -932400, -959040, -985680, -1012320, -1038960, -1065600, -1092240, -1118880, -1145520, -1172160, -1198800, -1225440, -1252080, + -1278720, -1305360, -1332000, -1358640, -1385280, -1411920, -1438560, -1465200, -1491840, -1518480, -1545120, -1571760, -1598400, -1625040, -1651680, -1678320, + -1704960, -1731600, -1758240, -1784880, -1811520, -1838160, -1864800, -1891440, -1918080, -1944720, -1971360, -1998000, -2024640, -2051280, -2077920, -2104560, + -2131200, -2157840, -2184480, -2211120, -2237760, -2264400, -2291040, -2317680, -2344320, -2370960, -2397600, -2424240, -2450880, -2477520, -2504160, -2530800, + -2557440, -2584080, -2610720, -2637360, -2664000, -2690640, -2717280, -2743920, -2770560, -2797200, -2823840, -2850480, -2877120, -2903760, -2930400, -2957040, + -2983680, -3010320, -3036960, -3063600, -3090240, -3116880, -3143520, -3170160, -3196800, -3223440, -3250080, -3276720, -3303360, -3330000, -3356640, -3383280, +}; +static const int COEFFS_BU[256] = { + -8464128, -8398002, -8331876, -8265750, -8199624, -8133498, -8067372, -8001246, -7935120, -7868994, -7802868, -7736742, -7670616, -7604490, -7538364, -7472238, + -7406112, -7339986, -7273860, -7207734, -7141608, -7075482, -7009356, -6943230, -6877104, -6810978, -6744852, -6678726, -6612600, -6546474, -6480348, -6414222, + -6348096, -6281970, -6215844, -6149718, -6083592, -6017466, -5951340, -5885214, -5819088, -5752962, -5686836, -5620710, -5554584, -5488458, -5422332, -5356206, + -5290080, -5223954, -5157828, -5091702, -5025576, -4959450, -4893324, -4827198, -4761072, -4694946, -4628820, -4562694, -4496568, -4430442, -4364316, -4298190, + -4232064, -4165938, -4099812, -4033686, -3967560, -3901434, -3835308, -3769182, -3703056, -3636930, -3570804, -3504678, -3438552, -3372426, -3306300, -3240174, + -3174048, -3107922, -3041796, -2975670, -2909544, -2843418, -2777292, -2711166, -2645040, -2578914, -2512788, -2446662, -2380536, -2314410, -2248284, -2182158, + -2116032, -2049906, -1983780, -1917654, -1851528, -1785402, -1719276, -1653150, -1587024, -1520898, -1454772, -1388646, -1322520, -1256394, -1190268, -1124142, + -1058016, -991890, -925764, -859638, -793512, -727386, -661260, -595134, -529008, -462882, -396756, -330630, -264504, -198378, -132252, -66126, + 0, 66126, 132252, 198378, 264504, 330630, 396756, 462882, 529008, 595134, 661260, 727386, 793512, 859638, 925764, 991890, + 1058016, 1124142, 1190268, 1256394, 1322520, 1388646, 1454772, 1520898, 1587024, 1653150, 1719276, 1785402, 1851528, 1917654, 1983780, 2049906, + 2116032, 2182158, 2248284, 2314410, 2380536, 2446662, 2512788, 2578914, 2645040, 2711166, 2777292, 2843418, 2909544, 2975670, 3041796, 3107922, + 3174048, 3240174, 3306300, 3372426, 3438552, 3504678, 3570804, 3636930, 3703056, 3769182, 3835308, 3901434, 3967560, 4033686, 4099812, 4165938, + 4232064, 4298190, 4364316, 4430442, 4496568, 4562694, 4628820, 4694946, 4761072, 4827198, 4893324, 4959450, 5025576, 5091702, 5157828, 5223954, + 5290080, 5356206, 5422332, 5488458, 5554584, 5620710, 5686836, 5752962, 5819088, 5885214, 5951340, 6017466, 6083592, 6149718, 6215844, 6281970, + 6348096, 6414222, 6480348, 6546474, 6612600, 6678726, 6744852, 6810978, 6877104, 6943230, 7009356, 7075482, 7141608, 7207734, 7273860, 7339986, + 7406112, 7472238, 7538364, 7604490, 7670616, 7736742, 7802868, 7868994, 7935120, 8001246, 8067372, 8133498, 8199624, 8265750, 8331876, 8398002, +}; +static const int CLAMP_TAB[1024] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +}; + +void YUVtoBGRA::translate(th_ycbcr_buffer &YUVBuffer, const th_info &theoraInfo, byte *pixelData, int pixelsSize) { + + // Width and height of all buffers have to be divisible by 2. + BS_ASSERT((YUVBuffer[0].width & 1) == 0); + BS_ASSERT((YUVBuffer[0].height & 1) == 0); + BS_ASSERT((YUVBuffer[1].width & 1) == 0); + BS_ASSERT((YUVBuffer[2].width & 1) == 0); + + // UV images have to have a quarter of the Y image resolution + BS_ASSERT(YUVBuffer[1].width == YUVBuffer[0].width >> 1); + BS_ASSERT(YUVBuffer[2].width == YUVBuffer[0].width >> 1); + BS_ASSERT(YUVBuffer[1].height == YUVBuffer[0].height >> 1); + BS_ASSERT(YUVBuffer[2].height == YUVBuffer[0].height >> 1); + + const int *cl = &CLAMP_TAB[320]; + + const byte *ySrc0 = YUVBuffer[0].data; + const byte *ySrc1 = YUVBuffer[0].data + YUVBuffer[0].stride; + const byte *uSrc = YUVBuffer[1].data; + const byte *vSrc = YUVBuffer[2].data; + byte *dst0 = &pixelData[0]; + byte *dst1 = &pixelData[0] + YUVBuffer[0].width * 4; + + for (int h = 0; h < YUVBuffer[0].height / 2; ++h) { + for (int w = 0; w < YUVBuffer[0].width / 2; ++w) { + int u = *uSrc++; + int v = *vSrc++; + + int rUV = COEFFS_RV[v]; + int gUV = COEFFS_GU[u] + COEFFS_GV[v]; + int bUV = COEFFS_BU[u]; + + int y = *ySrc0++; + int r = COEFFS_Y[y] + rUV; + int g = COEFFS_Y[y] + gUV; + int b = COEFFS_Y[y] + bUV; + *dst0++ = cl[b / PRECISION]; + *dst0++ = cl[g / PRECISION]; + *dst0++ = cl[r / PRECISION]; + *dst0++ = 255; + + y = *ySrc1++; + r = COEFFS_Y[y] + rUV; + g = COEFFS_Y[y] + gUV; + b = COEFFS_Y[y] + bUV; + *dst1++ = cl[b / PRECISION]; + *dst1++ = cl[g / PRECISION]; + *dst1++ = cl[r / PRECISION]; + *dst1++ = 255; + + y = *ySrc0++; + r = COEFFS_Y[y] + rUV; + g = COEFFS_Y[y] + gUV; + b = COEFFS_Y[y] + bUV; + *dst0++ = cl[b / PRECISION]; + *dst0++ = cl[g / PRECISION]; + *dst0++ = cl[r / PRECISION]; + *dst0++ = 255; + + y = *ySrc1++; + r = COEFFS_Y[y] + rUV; + g = COEFFS_Y[y] + gUV; + b = COEFFS_Y[y] + bUV; + *dst1++ = cl[b / PRECISION]; + *dst1++ = cl[g / PRECISION]; + *dst1++ = cl[r / PRECISION]; + *dst1++ = 255; + } + + dst0 += YUVBuffer[0].width * 4; + dst1 += YUVBuffer[0].width * 4; + ySrc0 += YUVBuffer[0].stride * 2 - YUVBuffer[0].width; + ySrc1 += YUVBuffer[0].stride * 2 - YUVBuffer[0].width; + uSrc += YUVBuffer[1].stride - YUVBuffer[1].width; + vSrc += YUVBuffer[2].stride - YUVBuffer[2].width; + } +} + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/fmv/yuvtorgba.h b/engines/sword25/fmv/yuvtorgba.h new file mode 100644 index 0000000000..675248e6e0 --- /dev/null +++ b/engines/sword25/fmv/yuvtorgba.h @@ -0,0 +1,56 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_YUVTORGBA_H +#define SWORD25_YUVTORGBA_H + +#include "common/scummsys.h" // for USE_THEORADEC + +#ifdef USE_THEORADEC +#include "sword25/kernel/common.h" +#include <theora/theora.h> +#include <theora/codec.h> + +namespace Sword25 { + +class YUVtoBGRA { +public: + static void translate(th_ycbcr_buffer &YUVBuffer, const th_info &theoraInfo, byte *pixelData, int pixelsSize); +}; + +} // End of namespace Sword25 + +#endif + +#endif diff --git a/engines/sword25/gfx/animation.cpp b/engines/sword25/gfx/animation.cpp new file mode 100644 index 0000000000..0d3baae347 --- /dev/null +++ b/engines/sword25/gfx/animation.cpp @@ -0,0 +1,680 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/animation.h" + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/resmanager.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/package/packagemanager.h" +#include "sword25/gfx/image/image.h" +#include "sword25/gfx/animationtemplate.h" +#include "sword25/gfx/animationtemplateregistry.h" +#include "sword25/gfx/animationresource.h" +#include "sword25/gfx/bitmapresource.h" +#include "sword25/gfx/graphicengine.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "ANIMATION" + +Animation::Animation(RenderObjectPtr<RenderObject> parentPtr, const Common::String &fileName) : + TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION) { + // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + initMembers(); + + // Vom negativen Fall ausgehen. + _initSuccess = false; + + initializeAnimationResource(fileName); + + // Erfolg signalisieren. + _initSuccess = true; +} + +Animation::Animation(RenderObjectPtr<RenderObject> parentPtr, const AnimationTemplate &templ) : + TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION) { + // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + initMembers(); + + // Vom negativen Fall ausgehen. + _initSuccess = false; + + _animationTemplateHandle = AnimationTemplate::create(templ); + + // Erfolg signalisieren. + _initSuccess = true; +} + +Animation::Animation(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION, handle) { + // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + initMembers(); + + // Objekt vom Stream laden. + _initSuccess = unpersist(reader); +} + +void Animation::initializeAnimationResource(const Common::String &fileName) { + // Die Resource wird für die gesamte Lebensdauer des Animations-Objektes gelockt. + Resource *resourcePtr = Kernel::getInstance()->getResourceManager()->requestResource(fileName); + if (resourcePtr && resourcePtr->getType() == Resource::TYPE_ANIMATION) + _animationResourcePtr = static_cast<AnimationResource *>(resourcePtr); + else { + BS_LOG_ERRORLN("The resource \"%s\" could not be requested. The Animation can't be created.", fileName.c_str()); + return; + } + + // Größe und Position der Animation anhand des aktuellen Frames bestimmen. + computeCurrentCharacteristics(); +} + +void Animation::initMembers() { + _currentFrame = 0; + _currentFrameTime = 0; + _direction = FORWARD; + _running = false; + _finished = false; + _relX = 0; + _relY = 0; + _scaleFactorX = 1.0f; + _scaleFactorY = 1.0f; + _modulationColor = 0xffffffff; + _animationResourcePtr = 0; + _animationTemplateHandle = 0; + _framesLocked = false; +} + +Animation::~Animation() { + if (getAnimationDescription()) { + stop(); + getAnimationDescription()->unlock(); + } + + // Invoke the "delete" callback + if (_deleteCallback) + (_deleteCallback)(getHandle()); + +} + +void Animation::play() { + // If the animation was completed, then play it again from the start. + if (_finished) + stop(); + + _running = true; + lockAllFrames(); +} + +void Animation::pause() { + _running = false; + unlockAllFrames(); +} + +void Animation::stop() { + _currentFrame = 0; + _currentFrameTime = 0; + _direction = FORWARD; + pause(); +} + +void Animation::setFrame(uint nr) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + + if (nr >= animationDescriptionPtr->getFrameCount()) { + BS_LOG_ERRORLN("Tried to set animation to illegal frame (%d). Value must be between 0 and %d.", + nr, animationDescriptionPtr->getFrameCount()); + return; + } + + _currentFrame = nr; + _currentFrameTime = 0; + computeCurrentCharacteristics(); + forceRefresh(); +} + +bool Animation::doRender() { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + BS_ASSERT(_currentFrame < animationDescriptionPtr->getFrameCount()); + + // Bitmap des aktuellen Frames holen + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(animationDescriptionPtr->getFrame(_currentFrame).fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmapResource = static_cast<BitmapResource *>(pResource); + + // Framebufferobjekt holen + GraphicEngine *pGfx = Kernel::getInstance()->getGfx(); + BS_ASSERT(pGfx); + + // Bitmap zeichnen + bool result; + if (isScalingAllowed() && (_width != pBitmapResource->getWidth() || _height != pBitmapResource->getHeight())) { + result = pBitmapResource->blit(_absoluteX, _absoluteY, + (animationDescriptionPtr->getFrame(_currentFrame).flipV ? BitmapResource::FLIP_V : 0) | + (animationDescriptionPtr->getFrame(_currentFrame).flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, _width, _height); + } else { + result = pBitmapResource->blit(_absoluteX, _absoluteY, + (animationDescriptionPtr->getFrame(_currentFrame).flipV ? BitmapResource::FLIP_V : 0) | + (animationDescriptionPtr->getFrame(_currentFrame).flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, -1, -1); + } + + // Resource freigeben + pBitmapResource->release(); + + return result; +} + +void Animation::frameNotification(int timeElapsed) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + BS_ASSERT(timeElapsed >= 0); + + // Nur wenn die Animation läuft wird sie auch weiterbewegt + if (_running) { + // Gesamte vergangene Zeit bestimmen (inkl. Restzeit des aktuellen Frames) + _currentFrameTime += timeElapsed; + + // Anzahl an zu überpringenden Frames bestimmen + int skipFrames = animationDescriptionPtr->getMillisPerFrame() == 0 ? 0 : _currentFrameTime / animationDescriptionPtr->getMillisPerFrame(); + + // Neue Frame-Restzeit bestimmen + _currentFrameTime -= animationDescriptionPtr->getMillisPerFrame() * skipFrames; + + // Neuen Frame bestimmen (je nach aktuellener Abspielrichtung wird addiert oder subtrahiert) + int tmpCurFrame = _currentFrame; + switch (_direction) { + case FORWARD: + tmpCurFrame += skipFrames; + break; + + case BACKWARD: + tmpCurFrame -= skipFrames; + break; + + default: + BS_ASSERT(0); + } + + // Deal with overflows + if (tmpCurFrame < 0) { + // Loop-Point callback + if (_loopPointCallback && !(_loopPointCallback)(getHandle())) + _loopPointCallback = 0; + + // An underflow may only occur if the animation type is JOJO. + BS_ASSERT(animationDescriptionPtr->getAnimationType() == AT_JOJO); + tmpCurFrame = - tmpCurFrame; + _direction = FORWARD; + } else if (static_cast<uint>(tmpCurFrame) >= animationDescriptionPtr->getFrameCount()) { + // Loop-Point callback + if (_loopPointCallback && !(_loopPointCallback)(getHandle())) + _loopPointCallback = 0; + + switch (animationDescriptionPtr->getAnimationType()) { + case AT_ONESHOT: + tmpCurFrame = animationDescriptionPtr->getFrameCount() - 1; + _finished = true; + pause(); + break; + + case AT_LOOP: + tmpCurFrame = tmpCurFrame % animationDescriptionPtr->getFrameCount(); + break; + + case AT_JOJO: + tmpCurFrame = animationDescriptionPtr->getFrameCount() - (tmpCurFrame % animationDescriptionPtr->getFrameCount()) - 1; + _direction = BACKWARD; + break; + + default: + BS_ASSERT(0); + } + } + + if ((int)_currentFrame != tmpCurFrame) { + forceRefresh(); + + if (animationDescriptionPtr->getFrame(_currentFrame).action != "") { + // action callback + if (_actionCallback && !(_actionCallback)(getHandle())) + _actionCallback = 0; + } + } + + _currentFrame = static_cast<uint>(tmpCurFrame); + } + + // Größe und Position der Animation anhand des aktuellen Frames bestimmen + computeCurrentCharacteristics(); + + BS_ASSERT(_currentFrame < animationDescriptionPtr->getFrameCount()); + BS_ASSERT(_currentFrameTime >= 0); +} + +void Animation::computeCurrentCharacteristics() { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); + + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(curFrame.fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmap = static_cast<BitmapResource *>(pResource); + + // Größe des Bitmaps auf die Animation übertragen + _width = static_cast<int>(pBitmap->getWidth() * _scaleFactorX); + _height = static_cast<int>(pBitmap->getHeight() * _scaleFactorY); + + // Position anhand des Hotspots berechnen und setzen + int posX = _relX + computeXModifier(); + int posY = _relY + computeYModifier(); + + RenderObject::setPos(posX, posY); + + pBitmap->release(); +} + +bool Animation::lockAllFrames() { + if (!_framesLocked) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + for (uint i = 0; i < animationDescriptionPtr->getFrameCount(); ++i) { + if (!Kernel::getInstance()->getResourceManager()->requestResource(animationDescriptionPtr->getFrame(i).fileName)) { + BS_LOG_ERRORLN("Could not lock all animation frames."); + return false; + } + } + + _framesLocked = true; + } + + return true; +} + +bool Animation::unlockAllFrames() { + if (_framesLocked) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + for (uint i = 0; i < animationDescriptionPtr->getFrameCount(); ++i) { + Resource *pResource; + if (!(pResource = Kernel::getInstance()->getResourceManager()->requestResource(animationDescriptionPtr->getFrame(i).fileName))) { + BS_LOG_ERRORLN("Could not unlock all animation frames."); + return false; + } + + // Zwei mal freigeben um den Request von LockAllFrames() und den jetzigen Request aufzuheben + pResource->release(); + if (pResource->getLockCount()) + pResource->release(); + } + + _framesLocked = false; + } + + return true; +} + +Animation::ANIMATION_TYPES Animation::getAnimationType() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getAnimationType(); +} + +int Animation::getFPS() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getFPS(); +} + +int Animation::getFrameCount() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getFrameCount(); +} + +bool Animation::isScalingAllowed() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->isScalingAllowed(); +} + +bool Animation::isAlphaAllowed() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->isAlphaAllowed(); +} + +bool Animation::isColorModulationAllowed() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->isColorModulationAllowed(); +} + +void Animation::setPos(int relX, int relY) { + _relX = relX; + _relY = relY; + + computeCurrentCharacteristics(); +} + +void Animation::setX(int relX) { + _relX = relX; + + computeCurrentCharacteristics(); +} + +void Animation::setY(int relY) { + _relY = relY; + + computeCurrentCharacteristics(); +} + +void Animation::setAlpha(int alpha) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isAlphaAllowed()) { + BS_LOG_WARNINGLN("Tried to set alpha value on an animation that does not support alpha. Call was ignored."); + return; + } + + uint newModulationColor = (_modulationColor & 0x00ffffff) | alpha << 24; + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Animation::setModulationColor(uint modulationColor) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isColorModulationAllowed()) { + BS_LOG_WARNINGLN("Tried to set modulation color on an animation that does not support color modulation. Call was ignored"); + return; + } + + uint newModulationColor = (modulationColor & 0x00ffffff) | (_modulationColor & 0xff000000); + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Animation::setScaleFactor(float scaleFactor) { + setScaleFactorX(scaleFactor); + setScaleFactorY(scaleFactor); +} + +void Animation::setScaleFactorX(float scaleFactorX) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set x scale factor on an animation that does not support scaling. Call was ignored"); + return; + } + + if (scaleFactorX != _scaleFactorX) { + _scaleFactorX = scaleFactorX; + if (_scaleFactorX <= 0.0f) + _scaleFactorX = 0.001f; + forceRefresh(); + computeCurrentCharacteristics(); + } +} + +void Animation::setScaleFactorY(float scaleFactorY) { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + if (!animationDescriptionPtr->isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set y scale factor on an animation that does not support scaling. Call was ignored"); + return; + } + + if (scaleFactorY != _scaleFactorY) { + _scaleFactorY = scaleFactorY; + if (_scaleFactorY <= 0.0f) + _scaleFactorY = 0.001f; + forceRefresh(); + computeCurrentCharacteristics(); + } +} + +const Common::String &Animation::getCurrentAction() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + return animationDescriptionPtr->getFrame(_currentFrame).action; +} + +int Animation::getX() const { + return _relX; +} + +int Animation::getY() const { + return _relY; +} + +int Animation::getAbsoluteX() const { + return _absoluteX + (_relX - _x); +} + +int Animation::getAbsoluteY() const { + return _absoluteY + (_relY - _y); +} + +int Animation::computeXModifier() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); + + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(curFrame.fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmap = static_cast<BitmapResource *>(pResource); + + int result = curFrame.flipV ? - static_cast<int>((pBitmap->getWidth() - 1 - curFrame.hotspotX) * _scaleFactorX) : + - static_cast<int>(curFrame.hotspotX * _scaleFactorX); + + pBitmap->release(); + + return result; +} + +int Animation::computeYModifier() const { + AnimationDescription *animationDescriptionPtr = getAnimationDescription(); + BS_ASSERT(animationDescriptionPtr); + const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); + + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(curFrame.fileName); + BS_ASSERT(pResource); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmap = static_cast<BitmapResource *>(pResource); + + int result = curFrame.flipH ? - static_cast<int>((pBitmap->getHeight() - 1 - curFrame.hotspotY) * _scaleFactorY) : + - static_cast<int>(curFrame.hotspotY * _scaleFactorY); + + pBitmap->release(); + + return result; +} + +bool Animation::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= RenderObject::persist(writer); + + writer.write(_relX); + writer.write(_relY); + writer.write(_scaleFactorX); + writer.write(_scaleFactorY); + writer.write(_modulationColor); + writer.write(_currentFrame); + writer.write(_currentFrameTime); + writer.write(_running); + writer.write(_finished); + writer.write(static_cast<uint>(_direction)); + + // Je nach Animationstyp entweder das Template oder die Ressource speichern. + if (_animationResourcePtr) { + uint marker = 0; + writer.write(marker); + writer.writeString(_animationResourcePtr->getFileName()); + } else if (_animationTemplateHandle) { + uint marker = 1; + writer.write(marker); + writer.write(_animationTemplateHandle); + } else { + BS_ASSERT(false); + } + + //writer.write(_AnimationDescriptionPtr); + + writer.write(_framesLocked); + + // The following is only there to for compatibility with older saves + // resp. the original engine. + writer.write((uint)1); + writer.writeString("LuaLoopPointCB"); + writer.write(getHandle()); + writer.write((uint)1); + writer.writeString("LuaActionCB"); + writer.write(getHandle()); + writer.write((uint)1); + writer.writeString("LuaDeleteCB"); + writer.write(getHandle()); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +// ----------------------------------------------------------------------------- + +bool Animation::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= RenderObject::unpersist(reader); + + reader.read(_relX); + reader.read(_relY); + reader.read(_scaleFactorX); + reader.read(_scaleFactorY); + reader.read(_modulationColor); + reader.read(_currentFrame); + reader.read(_currentFrameTime); + reader.read(_running); + reader.read(_finished); + uint direction; + reader.read(direction); + _direction = static_cast<Direction>(direction); + + // Animationstyp einlesen. + uint marker; + reader.read(marker); + if (marker == 0) { + Common::String resourceFilename; + reader.readString(resourceFilename); + initializeAnimationResource(resourceFilename); + } else if (marker == 1) { + reader.read(_animationTemplateHandle); + } else { + BS_ASSERT(false); + } + + reader.read(_framesLocked); + if (_framesLocked) + lockAllFrames(); + + + // The following is only there to for compatibility with older saves + // resp. the original engine. + uint callbackCount; + Common::String callbackFunctionName; + uint callbackData; + + // loop point callback + reader.read(callbackCount); + assert(callbackCount == 1); + reader.readString(callbackFunctionName); + assert(callbackFunctionName == "LuaLoopPointCB"); + reader.read(callbackData); + assert(callbackData == getHandle()); + + // loop point callback + reader.read(callbackCount); + assert(callbackCount == 1); + reader.readString(callbackFunctionName); + assert(callbackFunctionName == "LuaActionCB"); + reader.read(callbackData); + assert(callbackData == getHandle()); + + // loop point callback + reader.read(callbackCount); + assert(callbackCount == 1); + reader.readString(callbackFunctionName); + assert(callbackFunctionName == "LuaDeleteCB"); + reader.read(callbackData); + assert(callbackData == getHandle()); + + // Set the callbacks + setCallbacks(); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +// ----------------------------------------------------------------------------- + +AnimationDescription *Animation::getAnimationDescription() const { + if (_animationResourcePtr) + return _animationResourcePtr; + else + return AnimationTemplateRegistry::instance().resolveHandle(_animationTemplateHandle); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animation.h b/engines/sword25/gfx/animation.h new file mode 100644 index 0000000000..72fe7e2de8 --- /dev/null +++ b/engines/sword25/gfx/animation.h @@ -0,0 +1,219 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATION_H +#define SWORD25_ANIMATION_H + +// Includes +#include "sword25/kernel/common.h" +#include "sword25/gfx/timedrenderobject.h" + +namespace Sword25 { + +// Forward declarations +class Kernel; +class AnimationResource; +class AnimationTemplate; +class AnimationDescription; +class InputPersistenceBlock; + +class Animation : public TimedRenderObject { + friend class RenderObject; + +private: + Animation(RenderObjectPtr<RenderObject> parentPtr, const Common::String &fileName); + Animation(RenderObjectPtr<RenderObject> parentPtr, const AnimationTemplate &template_); + Animation(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + +public: + enum ANIMATION_TYPES { + AT_ONESHOT, + AT_LOOP, + AT_JOJO + }; + + virtual ~Animation(); + + void play(); + void pause(); + void stop(); + void setFrame(uint nr); + + virtual void setPos(int x, int y); + virtual void setX(int x); + virtual void setY(int y); + + virtual int getX() const; + virtual int getY() const; + virtual int getAbsoluteX() const; + virtual int getAbsoluteY() const; + + /** + @brief Setzt den Alphawert der Animation. + @param Alpha der neue Alphawert der Animation (0 = keine Deckung, 255 = volle Deckung). + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsAlphaAllowed() true zurückgibt. + */ + void setAlpha(int alpha); + + /** + @brief Setzt die Modulationfarbe der Animation. + @param Color eine 24-Bit Farbe, die die Modulationsfarbe der Animation festlegt. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsColorModulationAllowed() true zurückgibt. + */ + void setModulationColor(uint modulationColor); + + /** + @brief Setzt den Skalierungsfaktor der Animation. + @param ScaleFactor der Faktor um den die Animation in beide Richtungen gestreckt werden soll. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactor(float scaleFactor); + + /** + @brief Setzt den Skalierungsfaktor der Animation auf der X-Achse. + @param ScaleFactor der Faktor um den die Animation in Richtungen der X-Achse gestreckt werden soll. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactorX(float scaleFactorX); + + /** + @brief Setzt den Skalierungsfaktor der Animation auf der Y-Achse. + @param ScaleFactor der Faktor um den die Animation in Richtungen der Y-Achse gestreckt werden soll. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactorY(float scaleFactorY); + + /** + @brief Gibt den Skalierungsfakter der Animation auf der X-Achse zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + float getScaleFactorX() const { + return _scaleFactorX; + } + + /** + @brief Gibt den Skalierungsfakter der Animation auf der Y-Achse zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + float getScaleFactorY() const { + return _scaleFactorY; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + + virtual void frameNotification(int timeElapsed); + + ANIMATION_TYPES getAnimationType() const; + int getFPS() const; + int getFrameCount() const; + bool isScalingAllowed() const; + bool isAlphaAllowed() const; + bool isColorModulationAllowed() const; + uint getCurrentFrame() const { + return _currentFrame; + } + const Common::String &getCurrentAction() const; + bool isRunning() const { + return _running; + } + + typedef bool (*ANIMATION_CALLBACK)(uint); + + void setCallbacks(); + +protected: + virtual bool doRender(); + +private: + enum Direction { + FORWARD, + BACKWARD + }; + + int _relX; + int _relY; + float _scaleFactorX; + float _scaleFactorY; + uint _modulationColor; + uint _currentFrame; + int _currentFrameTime; + bool _running; + bool _finished; + Direction _direction; + AnimationResource *_animationResourcePtr; + uint _animationTemplateHandle; + bool _framesLocked; + + ANIMATION_CALLBACK _loopPointCallback; + ANIMATION_CALLBACK _actionCallback; + ANIMATION_CALLBACK _deleteCallback; + + /** + @brief Lockt alle Frames. + @return Gibt false zurück, falls nicht alle Frames gelockt werden konnten. + */ + bool lockAllFrames(); + + /** + @brief Unlockt alle Frames. + @return Gibt false zurück, falls nicht alles Frames freigegeben werden konnten. + */ + bool unlockAllFrames(); + + /** + @brief Diese Methode aktualisiert die Parameter (Größe, Position) der Animation anhand des aktuellen Frames. + + Diese Methode muss bei jedem Framewechsel aufgerufen werden damit der RenderObject-Manager immer aktuelle Daten hat. + */ + void computeCurrentCharacteristics(); + + /** + @brief Berechnet den Abstand zwischen dem linken Rand und dem Hotspot auf X-Achse in der aktuellen Darstellung. + */ + int computeXModifier() const; + + /** + @brief Berechnet den Abstand zwischen dem linken Rand und dem Hotspot auf X-Achse in der aktuellen Darstellung. + */ + int computeYModifier() const; + + void initMembers(); + AnimationDescription *getAnimationDescription() const; + void initializeAnimationResource(const Common::String &fileName); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/animationdescription.cpp b/engines/sword25/gfx/animationdescription.cpp new file mode 100644 index 0000000000..68ba7b63a6 --- /dev/null +++ b/engines/sword25/gfx/animationdescription.cpp @@ -0,0 +1,65 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/gfx/animationdescription.h" + +namespace Sword25 { + +bool AnimationDescription::persist(OutputPersistenceBlock &writer) { + writer.write(static_cast<uint>(_animationType)); + writer.write(_FPS); + writer.write(_millisPerFrame); + writer.write(_scalingAllowed); + writer.write(_alphaAllowed); + writer.write(_colorModulationAllowed); + + return true; +} + +bool AnimationDescription::unpersist(InputPersistenceBlock &reader) { + uint animationType; + reader.read(animationType); + _animationType = static_cast<Animation::ANIMATION_TYPES>(animationType); + reader.read(_FPS); + reader.read(_millisPerFrame); + reader.read(_scalingAllowed); + reader.read(_alphaAllowed); + reader.read(_colorModulationAllowed); + + return reader.isGood(); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animationdescription.h b/engines/sword25/gfx/animationdescription.h new file mode 100644 index 0000000000..88cbb23503 --- /dev/null +++ b/engines/sword25/gfx/animationdescription.h @@ -0,0 +1,103 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATIONDESCRIPTION_H +#define SWORD25_ANIMATIONDESCRIPTION_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "sword25/gfx/animation.h" + +namespace Sword25 { + +class AnimationDescription : public Persistable { +protected: + AnimationDescription() : + _animationType(Animation::AT_LOOP), + _FPS(10), + _millisPerFrame(0), + _scalingAllowed(true), + _alphaAllowed(true), + _colorModulationAllowed(true) + {} + +public: + struct Frame { + // Die Hotspot-Angabe bezieht sich auf das ungeflippte Bild!! + int hotspotX; + int hotspotY; + bool flipV; + bool flipH; + Common::String fileName; + Common::String action; + }; + + virtual const Frame &getFrame(uint index) const = 0; + virtual uint getFrameCount() const = 0; + virtual void unlock() = 0; + + Animation::ANIMATION_TYPES getAnimationType() const { + return _animationType; + } + int getFPS() const { + return _FPS; + } + int getMillisPerFrame() const { + return _millisPerFrame; + } + bool isScalingAllowed() const { + return _scalingAllowed; + } + bool isAlphaAllowed() const { + return _alphaAllowed; + } + bool isColorModulationAllowed() const { + return _colorModulationAllowed; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + Animation::ANIMATION_TYPES _animationType; + int _FPS; + int _millisPerFrame; + bool _scalingAllowed; + bool _alphaAllowed; + bool _colorModulationAllowed; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/animationresource.cpp b/engines/sword25/gfx/animationresource.cpp new file mode 100644 index 0000000000..6e5f683a2e --- /dev/null +++ b/engines/sword25/gfx/animationresource.cpp @@ -0,0 +1,252 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/animationresource.h" + +#include "sword25/kernel/kernel.h" +#include "sword25/package/packagemanager.h" +#include "sword25/gfx/bitmapresource.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "ANIMATIONRESOURCE" + +namespace { +const int DEFAULT_FPS = 10; +const int MIN_FPS = 1; +const int MAX_FPS = 200; +} + +AnimationResource::AnimationResource(const Common::String &filename) : + Resource(filename, Resource::TYPE_ANIMATION), + Common::XMLParser(), + _valid(false) { + // Get a pointer to the package manager + _pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(_pPackage); + + // Switch to the folder the specified Xml fiile is in + Common::String oldDirectory = _pPackage->getCurrentDirectory(); + if (getFileName().contains('/')) { + Common::String dir = Common::String(getFileName().c_str(), strrchr(getFileName().c_str(), '/')); + _pPackage->changeDirectory(dir); + } + + // Load the contents of the file + uint fileSize; + char *xmlData = _pPackage->getXmlFile(getFileName(), &fileSize); + if (!xmlData) { + BS_LOG_ERRORLN("Could not read \"%s\".", getFileName().c_str()); + return; + } + + // Parse the contents + if (!loadBuffer((const byte *)xmlData, fileSize)) + return; + + _valid = parse(); + close(); + free(xmlData); + + // Switch back to the previous folder + _pPackage->changeDirectory(oldDirectory); + + // Give an error message if there weren't any frames specified + if (_frames.empty()) { + BS_LOG_ERRORLN("\"%s\" does not have any frames.", getFileName().c_str()); + return; + } + + // Pre-cache all the frames + if (!precacheAllFrames()) { + BS_LOG_ERRORLN("Could not precache all frames of \"%s\".", getFileName().c_str()); + return; + } + + // Post processing to compute animation features + if (!computeFeatures()) { + BS_LOG_ERRORLN("Could not determine the features of \"%s\".", getFileName().c_str()); + return; + } + + _valid = true; +} + +bool AnimationResource::parseBooleanKey(Common::String s, bool &result) { + s.toLowercase(); + if (!strcmp(s.c_str(), "true")) + result = true; + else if (!strcmp(s.c_str(), "false")) + result = false; + else + return false; + return true; +} + +bool AnimationResource::parserCallback_animation(ParserNode *node) { + if (!parseIntegerKey(node->values["fps"], 1, &_FPS) || (_FPS < MIN_FPS) || (_FPS > MAX_FPS)) { + return parserError("Illegal or missing fps attribute in <animation> tag in \"%s\". Assuming default (\"%d\").", + getFileName().c_str(), DEFAULT_FPS); + } + + // Loop type value + const char *loopTypeString = node->values["type"].c_str(); + + if (strcmp(loopTypeString, "oneshot") == 0) { + _animationType = Animation::AT_ONESHOT; + } else if (strcmp(loopTypeString, "loop") == 0) { + _animationType = Animation::AT_LOOP; + } else if (strcmp(loopTypeString, "jojo") == 0) { + _animationType = Animation::AT_JOJO; + } else { + BS_LOG_WARNINGLN("Illegal type value (\"%s\") in <animation> tag in \"%s\". Assuming default (\"loop\").", + loopTypeString, getFileName().c_str()); + _animationType = Animation::AT_LOOP; + } + + // Calculate the milliseconds required per frame + // FIXME: Double check variable naming. Based on the constant, it may be microseconds + _millisPerFrame = 1000000 / _FPS; + + return true; +} + +bool AnimationResource::parserCallback_frame(ParserNode *node) { + Frame frame; + + const char *fileString = node->values["file"].c_str(); + if (!fileString) { + BS_LOG_ERRORLN("<frame> tag without file attribute occurred in \"%s\".", getFileName().c_str()); + return false; + } + frame.fileName = _pPackage->getAbsolutePath(fileString); + if (frame.fileName.empty()) { + BS_LOG_ERRORLN("Could not create absolute path for file specified in <frame> tag in \"%s\": \"%s\".", + getFileName().c_str(), fileString); + return false; + } + + const char *actionString = node->values["action"].c_str(); + if (actionString) + frame.action = actionString; + + const char *hotspotxString = node->values["hotspotx"].c_str(); + const char *hotspotyString = node->values["hotspoty"].c_str(); + if ((!hotspotxString && hotspotyString) || + (hotspotxString && !hotspotyString)) + BS_LOG_WARNINGLN("%s attribute occurred without %s attribute in <frame> tag in \"%s\". Assuming default (\"0\").", + hotspotxString ? "hotspotx" : "hotspoty", + !hotspotyString ? "hotspoty" : "hotspotx", + getFileName().c_str()); + + frame.hotspotX = 0; + if (hotspotxString && !parseIntegerKey(hotspotxString, 1, &frame.hotspotX)) + BS_LOG_WARNINGLN("Illegal hotspotx value (\"%s\") in frame tag in \"%s\". Assuming default (\"%s\").", + hotspotxString, getFileName().c_str(), frame.hotspotX); + + frame.hotspotY = 0; + if (hotspotyString && !parseIntegerKey(hotspotyString, 1, &frame.hotspotY)) + BS_LOG_WARNINGLN("Illegal hotspoty value (\"%s\") in frame tag in \"%s\". Assuming default (\"%s\").", + hotspotyString, getFileName().c_str(), frame.hotspotY); + + Common::String flipVString = node->values["flipv"]; + if (!flipVString.empty()) { + if (!parseBooleanKey(flipVString, frame.flipV)) { + BS_LOG_WARNINGLN("Illegal flipv value (\"%s\") in <frame> tag in \"%s\". Assuming default (\"false\").", + flipVString.c_str(), getFileName().c_str()); + frame.flipV = false; + } + } else + frame.flipV = false; + + Common::String flipHString = node->values["fliph"]; + if (!flipHString.empty()) { + if (!parseBooleanKey(flipVString, frame.flipV)) { + BS_LOG_WARNINGLN("Illegal fliph value (\"%s\") in <frame> tag in \"%s\". Assuming default (\"false\").", + flipHString.c_str(), getFileName().c_str()); + frame.flipH = false; + } + } else + frame.flipH = false; + + _frames.push_back(frame); + return true; +} + +AnimationResource::~AnimationResource() { +} + +bool AnimationResource::precacheAllFrames() const { + Common::Array<Frame>::const_iterator iter = _frames.begin(); + for (; iter != _frames.end(); ++iter) { + if (!Kernel::getInstance()->getResourceManager()->precacheResource((*iter).fileName)) { + BS_LOG_ERRORLN("Could not precache \"%s\".", (*iter).fileName.c_str()); + return false; + } + } + + return true; +} + +bool AnimationResource::computeFeatures() { + BS_ASSERT(_frames.size()); + + // Alle Features werden als vorhanden angenommen + _scalingAllowed = true; + _alphaAllowed = true; + _colorModulationAllowed = true; + + // Alle Frame durchgehen und alle Features deaktivieren, die auch nur von einem Frame nicht unterstützt werden. + Common::Array<Frame>::const_iterator iter = _frames.begin(); + for (; iter != _frames.end(); ++iter) { + BitmapResource *pBitmap; + if (!(pBitmap = static_cast<BitmapResource *>(Kernel::getInstance()->getResourceManager()->requestResource((*iter).fileName)))) { + BS_LOG_ERRORLN("Could not request \"%s\".", (*iter).fileName.c_str()); + return false; + } + + if (!pBitmap->isScalingAllowed()) + _scalingAllowed = false; + if (!pBitmap->isAlphaAllowed()) + _alphaAllowed = false; + if (!pBitmap->isColorModulationAllowed()) + _colorModulationAllowed = false; + + pBitmap->release(); + } + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animationresource.h b/engines/sword25/gfx/animationresource.h new file mode 100644 index 0000000000..da07b55c3b --- /dev/null +++ b/engines/sword25/gfx/animationresource.h @@ -0,0 +1,123 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATIONRESOURCE_H +#define SWORD25_ANIMATIONRESOURCE_H + +#include "common/xmlparser.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/resource.h" +#include "sword25/gfx/animationdescription.h" +#include "sword25/gfx/animation.h" + +namespace Sword25 { + +class Kernel; +class PackageManager; + +class AnimationResource : public Resource, public AnimationDescription, public Common::XMLParser { +public: + AnimationResource(const Common::String &filename); + virtual ~AnimationResource(); + + virtual const Frame &getFrame(uint index) const { + BS_ASSERT(index < _frames.size()); + return _frames[index]; + } + virtual uint getFrameCount() const { + return _frames.size(); + } + virtual void unlock() { + release(); + } + + Animation::ANIMATION_TYPES getAnimationType() const { + return _animationType; + } + int getFPS() const { + return _FPS; + } + int getMillisPerFrame() const { + return _millisPerFrame; + } + bool isScalingAllowed() const { + return _scalingAllowed; + } + bool isAlphaAllowed() const { + return _alphaAllowed; + } + bool isColorModulationAllowed() const { + return _colorModulationAllowed; + } + bool isValid() const { + return _valid; + } + +private: + bool _valid; + + Common::Array<Frame> _frames; + + PackageManager *_pPackage; + + + bool computeFeatures(); + bool precacheAllFrames() const; + + // Parser + CUSTOM_XML_PARSER(AnimationResource) { + XML_KEY(animation) + XML_PROP(fps, true) + XML_PROP(type, true) + + XML_KEY(frame) + XML_PROP(file, true) + XML_PROP(hotspotx, true) + XML_PROP(hotspoty, true) + XML_PROP(fliph, false) + XML_PROP(flipv, false) + KEY_END() + KEY_END() + } PARSER_END() + + bool parseBooleanKey(Common::String s, bool &result); + + // Parser callback methods + bool parserCallback_animation(ParserNode *node); + bool parserCallback_frame(ParserNode *node); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/animationtemplate.cpp b/engines/sword25/gfx/animationtemplate.cpp new file mode 100644 index 0000000000..4a060dbad9 --- /dev/null +++ b/engines/sword25/gfx/animationtemplate.cpp @@ -0,0 +1,243 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "ANIMATIONTEMPLATE" + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/resource.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +#include "sword25/gfx/animationresource.h" +#include "sword25/gfx/animationtemplate.h" +#include "sword25/gfx/animationtemplateregistry.h" + +namespace Sword25 { + +uint AnimationTemplate::create(const Common::String &sourceAnimation) { + AnimationTemplate *animationTemplatePtr = new AnimationTemplate(sourceAnimation); + + if (animationTemplatePtr->isValid()) { + return AnimationTemplateRegistry::instance().resolvePtr(animationTemplatePtr); + } else { + delete animationTemplatePtr; + return 0; + } +} + +uint AnimationTemplate::create(const AnimationTemplate &other) { + AnimationTemplate *animationTemplatePtr = new AnimationTemplate(other); + + if (animationTemplatePtr->isValid()) { + return AnimationTemplateRegistry::instance().resolvePtr(animationTemplatePtr); + } else { + delete animationTemplatePtr; + return 0; + } +} + +uint AnimationTemplate::create(InputPersistenceBlock &reader, uint handle) { + AnimationTemplate *animationTemplatePtr = new AnimationTemplate(reader, handle); + + if (animationTemplatePtr->isValid()) { + return AnimationTemplateRegistry::instance().resolvePtr(animationTemplatePtr); + } else { + delete animationTemplatePtr; + return 0; + } +} + +AnimationTemplate::AnimationTemplate(const Common::String &sourceAnimation) { + // Objekt registrieren. + AnimationTemplateRegistry::instance().registerObject(this); + + _valid = false; + + // Die Animations-Resource wird für die gesamte Lebensdauer des Objektes gelockt + _sourceAnimationPtr = requestSourceAnimation(sourceAnimation); + + // Erfolg signalisieren + _valid = (_sourceAnimationPtr != 0); +} + +AnimationTemplate::AnimationTemplate(const AnimationTemplate &other) : AnimationDescription() { + // Objekt registrieren. + AnimationTemplateRegistry::instance().registerObject(this); + + _valid = false; + + // Die Animations-Resource wird für die gesamte Lebensdauer des Objektes gelockt. + if (!other._sourceAnimationPtr) + return; + _sourceAnimationPtr = requestSourceAnimation(other._sourceAnimationPtr->getFileName()); + + // Alle Member kopieren. + _animationType = other._animationType; + _FPS = other._FPS; + _millisPerFrame = other._millisPerFrame; + _scalingAllowed = other._scalingAllowed; + _alphaAllowed = other._alphaAllowed; + _colorModulationAllowed = other._colorModulationAllowed; + _frames = other._frames; + _sourceAnimationPtr = other._sourceAnimationPtr; + _valid = other._valid; + + _valid &= (_sourceAnimationPtr != 0); +} + +AnimationTemplate::AnimationTemplate(InputPersistenceBlock &reader, uint handle) { + // Objekt registrieren. + AnimationTemplateRegistry::instance().registerObject(this, handle); + + // Objekt laden. + _valid = unpersist(reader); +} + +AnimationResource *AnimationTemplate::requestSourceAnimation(const Common::String &sourceAnimation) const { + ResourceManager *RMPtr = Kernel::getInstance()->getResourceManager(); + Resource *resourcePtr; + if (NULL == (resourcePtr = RMPtr->requestResource(sourceAnimation)) || resourcePtr->getType() != Resource::TYPE_ANIMATION) { + BS_LOG_ERRORLN("The resource \"%s\" could not be requested or is has an invalid type. The animation template can't be created.", sourceAnimation.c_str()); + return 0; + } + return static_cast<AnimationResource *>(resourcePtr); +} + +AnimationTemplate::~AnimationTemplate() { + // Animations-Resource freigeben + if (_sourceAnimationPtr) { + _sourceAnimationPtr->release(); + } + + // Objekt deregistrieren + AnimationTemplateRegistry::instance().deregisterObject(this); +} + +void AnimationTemplate::addFrame(int index) { + if (validateSourceIndex(index)) { + _frames.push_back(_sourceAnimationPtr->getFrame(index)); + } +} + +void AnimationTemplate::setFrame(int destIndex, int srcIndex) { + if (validateDestIndex(destIndex) && validateSourceIndex(srcIndex)) { + _frames[destIndex] = _sourceAnimationPtr->getFrame(srcIndex); + } +} + +bool AnimationTemplate::validateSourceIndex(uint index) const { + if (index > _sourceAnimationPtr->getFrameCount()) { + BS_LOG_WARNINGLN("Tried to insert a frame (\"%d\") that does not exist in the source animation (\"%s\"). Ignoring call.", + index, _sourceAnimationPtr->getFileName().c_str()); + return false; + } else + return true; +} + +bool AnimationTemplate::validateDestIndex(uint index) const { + if (index > _frames.size()) { + BS_LOG_WARNINGLN("Tried to change a nonexistent frame (\"%d\") in a template animation. Ignoring call.", + index); + return false; + } else + return true; +} + +void AnimationTemplate::setFPS(int FPS) { + _FPS = FPS; + _millisPerFrame = 1000000 / _FPS; +} + +bool AnimationTemplate::persist(OutputPersistenceBlock &writer) { + bool Result = true; + + // Parent persistieren. + Result &= AnimationDescription::persist(writer); + + // Frameanzahl schreiben. + writer.write(_frames.size()); + + // Frames einzeln persistieren. + Common::Array<const Frame>::const_iterator Iter = _frames.begin(); + while (Iter != _frames.end()) { + writer.write(Iter->hotspotX); + writer.write(Iter->hotspotY); + writer.write(Iter->flipV); + writer.write(Iter->flipH); + writer.writeString(Iter->fileName); + writer.writeString(Iter->action); + ++Iter; + } + + // Restliche Member persistieren. + writer.writeString(_sourceAnimationPtr->getFileName()); + writer.write(_valid); + + return Result; +} + +bool AnimationTemplate::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + // Parent wieder herstellen. + result &= AnimationDescription::unpersist(reader); + + // Frameanzahl lesen. + uint frameCount; + reader.read(frameCount); + + // Frames einzeln wieder herstellen. + for (uint i = 0; i < frameCount; ++i) { + Frame frame; + reader.read(frame.hotspotX); + reader.read(frame.hotspotY); + reader.read(frame.flipV); + reader.read(frame.flipH); + reader.readString(frame.fileName); + reader.readString(frame.action); + + _frames.push_back(frame); + } + + // Die Animations-Resource wird für die gesamte Lebensdauer des Objektes gelockt + Common::String sourceAnimation; + reader.readString(sourceAnimation); + _sourceAnimationPtr = requestSourceAnimation(sourceAnimation); + + reader.read(_valid); + + return _sourceAnimationPtr && reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animationtemplate.h b/engines/sword25/gfx/animationtemplate.h new file mode 100644 index 0000000000..294f249f81 --- /dev/null +++ b/engines/sword25/gfx/animationtemplate.h @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATION_TEMPLATE_H +#define SWORD25_ANIMATION_TEMPLATE_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "sword25/gfx/animationdescription.h" + +namespace Sword25 { + +class AnimationResource; + +class AnimationTemplate : public AnimationDescription { +public: + static uint create(const Common::String &sourceAnimation); + static uint create(const AnimationTemplate &other); + static uint create(InputPersistenceBlock &reader, uint handle); + AnimationTemplate *resolveHandle(uint handle) const; + +private: + AnimationTemplate(const Common::String &sourceAnimation); + AnimationTemplate(const AnimationTemplate &other); + AnimationTemplate(InputPersistenceBlock &reader, uint handle); + +public: + ~AnimationTemplate(); + + virtual const Frame &getFrame(uint index) const { + BS_ASSERT(index < _frames.size()); + return _frames[index]; + } + virtual uint getFrameCount() const { + return _frames.size(); + } + virtual void unlock() { + delete this; + } + + bool isValid() const { + return _valid; + } + + /** + @brief Fügt einen neuen Frame zur Animation hinzu. + + Der Frame wird an das Ende der Animation angehängt. + + @param Index der Index des Frames in der Quellanimation + */ + void addFrame(int index); + + /** + @brief Ändert einen bereits in der Animation vorhandenen Frame. + @param DestIndex der Index des Frames der überschrieben werden soll + @param SrcIndex der Index des einzufügenden Frames in der Quellanimation + */ + void setFrame(int destIndex, int srcIndex); + + /** + @brief Setzt den Animationstyp. + @param Type der Typ der Animation. Muss aus den enum BS_Animation::ANIMATION_TYPES sein. + */ + void setAnimationType(Animation::ANIMATION_TYPES type) { + _animationType = type; + } + + /** + @brief Setzt die Abspielgeschwindigkeit. + @param FPS die Abspielgeschwindigkeit in Frames pro Sekunde. + */ + void setFPS(int FPS); + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + Common::Array<Frame> _frames; + AnimationResource *_sourceAnimationPtr; + bool _valid; + + AnimationResource *requestSourceAnimation(const Common::String &sourceAnimation) const; + bool validateSourceIndex(uint index) const; + bool validateDestIndex(uint index) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/animationtemplateregistry.cpp b/engines/sword25/gfx/animationtemplateregistry.cpp new file mode 100644 index 0000000000..ac1d6096f4 --- /dev/null +++ b/engines/sword25/gfx/animationtemplateregistry.cpp @@ -0,0 +1,107 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "ANIMATIONTEMPLATEREGISTRY" + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/gfx/animationtemplateregistry.h" +#include "sword25/gfx/animationtemplate.h" + +DECLARE_SINGLETON(Sword25::AnimationTemplateRegistry) + +namespace Sword25 { + +void AnimationTemplateRegistry::logErrorLn(const char *message) const { + BS_LOG_ERRORLN(message); +} + +void AnimationTemplateRegistry::logWarningLn(const char *message) const { + BS_LOG_WARNINGLN(message); +} + +bool AnimationTemplateRegistry::persist(OutputPersistenceBlock &writer) { + bool result = true; + + // Das nächste zu vergebene Handle schreiben. + writer.write(_nextHandle); + + // Anzahl an BS_AnimationTemplates schreiben. + writer.write(_handle2PtrMap.size()); + + // Alle BS_AnimationTemplates persistieren. + HANDLE2PTR_MAP::const_iterator iter = _handle2PtrMap.begin(); + while (iter != _handle2PtrMap.end()) { + // Handle persistieren. + writer.write(iter->_key); + + // Objekt persistieren. + result &= iter->_value->persist(writer); + + ++iter; + } + + return result; +} + +// ----------------------------------------------------------------------------- + +bool AnimationTemplateRegistry::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + // Das nächste zu vergebene Handle wieder herstellen. + reader.read(_nextHandle); + + // Alle vorhandenen BS_AnimationTemplates zerstören. + while (!_handle2PtrMap.empty()) + delete _handle2PtrMap.begin()->_value; + + // Anzahl an BS_AnimationTemplates einlesen. + uint animationTemplateCount; + reader.read(animationTemplateCount); + + // Alle gespeicherten BS_AnimationTemplates wieder herstellen. + for (uint i = 0; i < animationTemplateCount; ++i) { + // Handle lesen. + uint handle; + reader.read(handle); + + // BS_AnimationTemplate wieder herstellen. + result &= (AnimationTemplate::create(reader, handle) != 0); + } + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/animationtemplateregistry.h b/engines/sword25/gfx/animationtemplateregistry.h new file mode 100644 index 0000000000..c5308bb124 --- /dev/null +++ b/engines/sword25/gfx/animationtemplateregistry.h @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ANIMATIONTEMPLATEREGISTRY_H +#define SWORD25_ANIMATIONTEMPLATEREGISTRY_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "sword25/kernel/objectregistry.h" + +#include "common/singleton.h" + +namespace Sword25 { + +class AnimationTemplate; + +class AnimationTemplateRegistry : + public ObjectRegistry<AnimationTemplate>, + public Persistable, + public Common::Singleton<AnimationTemplateRegistry> { +public: + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + virtual void logErrorLn(const char *message) const; + virtual void logWarningLn(const char *message) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/bitmap.cpp b/engines/sword25/gfx/bitmap.cpp new file mode 100644 index 0000000000..b5412c8276 --- /dev/null +++ b/engines/sword25/gfx/bitmap.cpp @@ -0,0 +1,183 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/bitmap.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "BITMAP" + +Bitmap::Bitmap(RenderObjectPtr<RenderObject> parentPtr, TYPES type, uint handle) : + RenderObject(parentPtr, type, handle), + _modulationColor(0xffffffff), + _scaleFactorX(1.0f), + _scaleFactorY(1.0f), + _flipH(false), + _flipV(false) { +} + +Bitmap::~Bitmap() { +} + +void Bitmap::setAlpha(int alpha) { + if (!isAlphaAllowed()) { + BS_LOG_WARNINGLN("Tried to set alpha value on a bitmap that does not support alpha blending. Call was ignored."); + return; + } + + if (alpha < 0 || alpha > 255) { + int oldAlpha = alpha; + if (alpha < 0) + alpha = 0; + if (alpha > 255) + alpha = 255; + BS_LOG_WARNINGLN("Tried to set an invalid alpha value (%d) on a bitmap. Value was changed to %d.", oldAlpha, alpha); + + return; + } + + uint newModulationColor = (_modulationColor & 0x00ffffff) | alpha << 24; + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Bitmap::setModulationColor(uint modulationColor) { + if (!isColorModulationAllowed()) { + BS_LOG_WARNINGLN("Tried to set modulation color of a bitmap that does not support color modulation. Call was ignored."); + return; + } + + uint newModulationColor = (modulationColor & 0x00ffffff) | (_modulationColor & 0xff000000); + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Bitmap::setScaleFactor(float scaleFactor) { + setScaleFactorX(scaleFactor); + setScaleFactorY(scaleFactor); +} + +void Bitmap::setScaleFactorX(float scaleFactorX) { + if (!isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set scale factor of a bitmap that does not support scaling. Call was ignored."); + return; + } + + if (scaleFactorX < 0) { + BS_LOG_WARNINGLN("Tried to set scale factor of a bitmap to a negative value. Call was ignored."); + return; + } + + if (scaleFactorX != _scaleFactorX) { + _scaleFactorX = scaleFactorX; + _width = static_cast<int>(_originalWidth * _scaleFactorX); + if (_scaleFactorX <= 0.0f) + _scaleFactorX = 0.001f; + if (_width <= 0) + _width = 1; + forceRefresh(); + } +} + +void Bitmap::setScaleFactorY(float scaleFactorY) { + if (!isScalingAllowed()) { + BS_LOG_WARNINGLN("Tried to set scale factor of a bitmap that does not support scaling. Call was ignored."); + return; + } + + if (scaleFactorY < 0) { + BS_LOG_WARNINGLN("Tried to set scale factor of a bitmap to a negative value. Call was ignored."); + return; + } + + if (scaleFactorY != _scaleFactorY) { + _scaleFactorY = scaleFactorY; + _height = static_cast<int>(_originalHeight * scaleFactorY); + if (_scaleFactorY <= 0.0f) + _scaleFactorY = 0.001f; + if (_height <= 0) + _height = 1; + forceRefresh(); + } +} + +void Bitmap::setFlipH(bool flipH) { + _flipH = flipH; + forceRefresh(); +} + +void Bitmap::setFlipV(bool flipV) { + _flipV = flipV; + forceRefresh(); +} + +bool Bitmap::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= RenderObject::persist(writer); + writer.write(_flipH); + writer.write(_flipV); + writer.write(_scaleFactorX); + writer.write(_scaleFactorY); + writer.write(_modulationColor); + writer.write(_originalWidth); + writer.write(_originalHeight); + + return result; +} + +bool Bitmap::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= RenderObject::unpersist(reader); + reader.read(_flipH); + reader.read(_flipV); + reader.read(_scaleFactorX); + reader.read(_scaleFactorY); + reader.read(_modulationColor); + reader.read(_originalWidth); + reader.read(_originalHeight); + + forceRefresh(); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/bitmap.h b/engines/sword25/gfx/bitmap.h new file mode 100644 index 0000000000..741269c423 --- /dev/null +++ b/engines/sword25/gfx/bitmap.h @@ -0,0 +1,189 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_BITMAP_H +#define SWORD25_BITMAP_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +class Bitmap : public RenderObject { +protected: + Bitmap(RenderObjectPtr<RenderObject> parentPtr, TYPES type, uint handle = 0); + +public: + + virtual ~Bitmap(); + + /** + @brief Setzt den Alphawert des Bitmaps. + @param Alpha der neue Alphawert der Bitmaps (0 = keine Deckung, 255 = volle Deckung). + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsAlphaAllowed() true zurückgibt. + */ + void setAlpha(int alpha); + + /** + @brief Setzt die Modulationfarbe der Bitmaps. + @param Color eine 24-Bit Farbe, die die Modulationsfarbe des Bitmaps festlegt. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsColorModulationAllowed() true zurückgibt. + */ + void setModulationColor(uint modulationColor); + + /** + @brief Setzt den Skalierungsfaktor des Bitmaps. + @param ScaleFactor der Faktor um den das Bitmap in beide Richtungen gestreckt werden soll. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactor(float scaleFactor); + + /** + @brief Setzt den Skalierungsfaktor der Bitmap auf der X-Achse. + @param ScaleFactor der Faktor um den die Bitmap in Richtungen der X-Achse gestreckt werden soll. Dieser Wert muss positiv sein. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactorX(float scaleFactorX); + + /** + @brief Setzt den Skalierungsfaktor der Bitmap auf der Y-Achse. + @param ScaleFactor der Faktor um den die Bitmap in Richtungen der Y-Achse gestreckt werden soll. Dieser Wert muss positiv sein. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + void setScaleFactorY(float scaleFactorY); + + /** + @brief Legt fest, ob das Bild an der X-Achse gespiegelt werden soll. + */ + void setFlipH(bool flipH); + + /** + @brief Legt fest, ob das Bild an der Y-Achse gespiegelt werden soll. + */ + void setFlipV(bool flipV); + + /** + @brief Gibt den aktuellen Alphawert des Bildes zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsAlphaAllowed() true zurückgibt. + */ + int getAlpha() { + return _modulationColor >> 24; + } + + /** + @brief Gibt die aktuelle 24bit RGB Modulationsfarde des Bildes zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsColorModulationAllowed() true zurückgibt. + */ + int getModulationColor() { + return _modulationColor & 0x00ffffff; + } + + /** + @brief Gibt den Skalierungsfakter des Bitmaps auf der X-Achse zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + float getScaleFactorX() const { + return _scaleFactorX; + } + + /** + @brief Gibt den Skalierungsfakter des Bitmaps auf der Y-Achse zurück. + @remark Diese Methode darf nur aufgerufen werden, wenn die Methode IsScalingAllowed() true zurückgibt. + */ + float getScaleFactorY() const { + return _scaleFactorY; + } + + /** + @brief Gibt zurück, ob das Bild an der X-Achse gespiegelt angezeigt wird. + */ + bool isFlipH() { + return _flipH; + } + + /** + @brief Gibt zurück, ob das Bild an der Y-Achse gespiegelt angezeigt wird. + */ + bool isFlipV() { + return _flipV; + } + + // ----------------------------------------------------------------------------- + // Die folgenden Methoden müssen alle BS_Bitmap-Klassen implementieren + // ----------------------------------------------------------------------------- + + /** + @brief Liest einen Pixel des Bildes. + @param X die X-Koordinate des Pixels. + @param Y die Y-Koordinate des Pixels + @return Gibt den 32-Bit Farbwert des Pixels an der übergebenen Koordinate zurück. + @remark Diese Methode sollte auf keine Fall benutzt werden um größere Teile des Bildes zu lesen, da sie sehr langsam ist. Sie ist + eher dafür gedacht einzelne Pixel des Bildes auszulesen. + */ + virtual uint getPixel(int x, int y) const = 0; + + /** + @brief Füllt den Inhalt des Bildes mit Pixeldaten. + @param Pixeldata ein Vector der die Pixeldaten enthält. Sie müssen in dem Farbformat des Bildes vorliegen und es müssen genügend Daten + vorhanden sein, um das ganze Bild zu füllen. + @param Offset der Offset in Byte im Pixeldata-Vector an dem sich der erste zu schreibende Pixel befindet.<br> + Der Standardwert ist 0. + @param Stride der Abstand in Byte zwischen dem Zeilenende und dem Beginn einer neuen Zeile im Pixeldata-Vector.<br> + Der Standardwert ist 0. + @return Gibt false zurück, falls der Aufruf fehlgeschlagen ist. + @remark Ein Aufruf dieser Methode ist nur erlaubt, wenn IsSetContentAllowed() true zurückgibt. + */ + virtual bool setContent(const byte *pixeldata, uint size, uint offset = 0, uint stride = 0) = 0; + + virtual bool isScalingAllowed() const = 0; + virtual bool isAlphaAllowed() const = 0; + virtual bool isColorModulationAllowed() const = 0; + virtual bool isSetContentAllowed() const = 0; + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + bool _flipH; + bool _flipV; + float _scaleFactorX; + float _scaleFactorY; + uint _modulationColor; + int _originalWidth; + int _originalHeight; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/bitmapresource.cpp b/engines/sword25/gfx/bitmapresource.cpp new file mode 100644 index 0000000000..cf3c85e3ac --- /dev/null +++ b/engines/sword25/gfx/bitmapresource.cpp @@ -0,0 +1,62 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/bitmapresource.h" +#include "sword25/kernel/kernel.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/package/packagemanager.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "BITMAP" + +BitmapResource::BitmapResource(const Common::String &filename, Image *pImage) : + _valid(false), + _pImage(pImage), + Resource(filename, Resource::TYPE_BITMAP) { + _valid = _pImage != 0; +} + +BitmapResource::~BitmapResource() { + delete _pImage; +} + +uint BitmapResource::getPixel(int x, int y) const { + BS_ASSERT(x >= 0 && x < _pImage->getWidth()); + BS_ASSERT(y >= 0 && y < _pImage->getHeight()); + + return _pImage->getPixel(x, y); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/bitmapresource.h b/engines/sword25/gfx/bitmapresource.h new file mode 100644 index 0000000000..37849f918e --- /dev/null +++ b/engines/sword25/gfx/bitmapresource.h @@ -0,0 +1,212 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_BITMAP_RESOURCE_H +#define SWORD25_BITMAP_RESOURCE_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/resource.h" +#include "sword25/gfx/image/image.h" + +namespace Sword25 { + +class BitmapResource : public Resource { +public: + /** + @brief Die möglichen Flippingparameter für die Blit-Methode. + */ + enum FLIP_FLAGS { + /// Das Bild wird nicht gespiegelt. + FLIP_NONE = 0, + /// Das Bild wird an der horizontalen Achse gespiegelt. + FLIP_H = 1, + /// Das Bild wird an der vertikalen Achse gespiegelt. + FLIP_V = 2, + /// Das Bild wird an der horizontalen und vertikalen Achse gespiegelt. + FLIP_HV = FLIP_H | FLIP_V, + /// Das Bild wird an der horizontalen und vertikalen Achse gespiegelt. + FLIP_VH = FLIP_H | FLIP_V + }; + + BitmapResource(const Common::String &filename, Image *pImage); + virtual ~BitmapResource(); + + /** + @brief Gibt zurück, ob das Objekt einen gültigen Zustand hat. + */ + bool isValid() const { + return _valid; + } + + /** + @brief Gibt die Breite des Bitmaps zurück. + */ + int getWidth() const { + BS_ASSERT(_pImage); + return _pImage->getWidth(); + } + + /** + @brief Gibt die Höhe des Bitmaps zurück. + */ + int getHeight() const { + BS_ASSERT(_pImage); + return _pImage->getHeight(); + } + + /** + @brief Rendert das Bild in den Framebuffer. + @param PosX die Position auf der X-Achse im Zielbild in Pixeln, an der das Bild gerendert werden soll.<br> + Der Standardwert ist 0. + @param PosY die Position auf der Y-Achse im Zielbild in Pixeln, an der das Bild gerendert werden soll.<br> + Der Standardwert ist 0. + @param Flipping gibt an, wie das Bild gespiegelt werden soll.<br> + Der Standardwert ist BS_Image::FLIP_NONE (keine Spiegelung) + @param pSrcPartRect Pointer auf ein Common::Rect, welches den Ausschnitt des Quellbildes spezifiziert, der gerendert + werden soll oder NULL, falls das gesamte Bild gerendert werden soll.<br> + Dieser Ausschnitt bezieht sich auf das ungespiegelte und unskalierte Bild.<br> + Der Standardwert ist NULL. + @param Color ein ARGB Farbwert, der die Parameter für die Farbmodulation und fürs Alphablending festlegt.<br> + Die Alpha-Komponente der Farbe bestimmt den Alphablending Parameter (0 = keine Deckung, 255 = volle Deckung).<br> + Die Farbkomponenten geben die Farbe für die Farbmodulation an.<br> + Der Standardwert is BS_ARGB(255, 255, 255, 255) (volle Deckung, keine Farbmodulation). + Zum Erzeugen des Farbwertes können die Makros BS_RGB und BS_ARGB benutzt werden. + @param Width gibt die Ausgabebreite des Bildausschnittes an. + Falls diese von der Breite des Bildausschnittes abweicht wird + das Bild entsprechend Skaliert.<br> + Der Wert -1 gibt an, dass das Bild nicht Skaliert werden soll.<br> + Der Standardwert ist -1. + @param Width gibt die Ausgabehöhe des Bildausschnittes an. + Falls diese von der Höhe des Bildauschnittes abweicht, wird + das Bild entsprechend Skaliert.<br> + Der Wert -1 gibt an, dass das Bild nicht Skaliert werden soll.<br> + Der Standardwert ist -1. + @return Gibt false zurück, falls das Rendern fehlgeschlagen ist. + @remark Er werden nicht alle Blitting-Operationen von allen BS_Image-Klassen unterstützt.<br> + Mehr Informationen gibt es in der Klassenbeschreibung von BS_Image und durch folgende Methoden: + - IsBlitTarget() + - IsScalingAllowed() + - IsFillingAllowed() + - IsAlphaAllowed() + - IsColorModulationAllowed() + */ + bool blit(int posX = 0, int posY = 0, + int flipping = FLIP_NONE, + Common::Rect *pSrcPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1) { + BS_ASSERT(_pImage); + return _pImage->blit(posX, posY, flipping, pSrcPartRect, color, width, height); + } + + /** + @brief Füllt einen Rechteckigen Bereich des Bildes mit einer Farbe. + @param pFillRect Pointer auf ein Common::Rect, welches den Ausschnitt des Bildes spezifiziert, der gefüllt + werden soll oder NULL, falls das gesamte Bild gefüllt werden soll.<br> + Der Standardwert ist NULL. + @param Color der 32 Bit Farbwert mit dem der Bildbereich gefüllt werden soll. + @remark Ein Aufruf dieser Methode ist nur gestattet, wenn IsFillingAllowed() true zurückgibt. + @remark Es ist möglich über die Methode transparente Rechtecke darzustellen, indem man eine Farbe mit einem Alphawert ungleich + 255 angibt. + @remark Unabhängig vom Farbformat des Bildes muss ein 32 Bit Farbwert angegeben werden. Zur Erzeugung, können die Makros + BS_RGB und BS_ARGB benutzt werden. + @remark Falls das Rechteck nicht völlig innerhalb des Bildschirms ist, wird es automatisch zurechtgestutzt. + */ + bool fill(const Common::Rect *pFillRect = 0, uint color = BS_RGB(0, 0, 0)) { + BS_ASSERT(_pImage); + return _pImage->fill(pFillRect, color); + } + + /** + @brief Liest einen Pixel des Bildes. + @param X die X-Koordinate des Pixels. + @param Y die Y-Koordinate des Pixels + @return Gibt den 32-Bit Farbwert des Pixels an der übergebenen Koordinate zurück. + @remark Diese Methode sollte auf keine Fall benutzt werden um größere Teile des Bildes zu lesen, da sie sehr langsam ist. Sie ist + eher dafür gedacht einzelne Pixel des Bildes auszulesen. + */ + uint getPixel(int x, int y) const; + + //@{ + /** @name Auskunfts-Methoden */ + + /** + @brief Überprüft, ob das BS_Image ein Zielbild für einen Blit-Aufruf sein kann. + @return Gibt false zurück, falls ein Blit-Aufruf mit diesem Objekt als Ziel nicht gestattet ist. + */ + bool isBlitTarget() { + BS_ASSERT(_pImage); + return _pImage->isBlitTarget(); + } + + /** + @brief Gibt true zurück, falls das BS_Image bei einem Aufruf von Blit() skaliert dargestellt werden kann. + */ + bool isScalingAllowed() { + BS_ASSERT(_pImage); + return _pImage->isScalingAllowed(); + } + + /** + @brief Gibt true zurück, wenn das BS_Image mit einem Aufruf von Fill() gefüllt werden kann. + */ + bool isFillingAllowed() { + BS_ASSERT(_pImage); + return _pImage->isFillingAllowed(); + } + + /** + @brief Gibt true zurück, wenn das BS_Image bei einem Aufruf von Blit() mit einem Alphawert dargestellt werden kann. + */ + bool isAlphaAllowed() { + BS_ASSERT(_pImage); + return _pImage->isAlphaAllowed(); + } + + /** + @brief Gibt true zurück, wenn das BS_Image bei einem Aufruf von Blit() mit Farbmodulation dargestellt werden kann. + */ + bool isColorModulationAllowed() { + BS_ASSERT(_pImage); + return _pImage->isColorModulationAllowed(); + } + +private: + Image *_pImage; + bool _valid; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/dynamicbitmap.cpp b/engines/sword25/gfx/dynamicbitmap.cpp new file mode 100644 index 0000000000..612e370712 --- /dev/null +++ b/engines/sword25/gfx/dynamicbitmap.cpp @@ -0,0 +1,155 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/dynamicbitmap.h" +#include "sword25/gfx/bitmapresource.h" +#include "sword25/package/packagemanager.h" +#include "sword25/kernel/inputpersistenceblock.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "DYNAMICBITMAP" + +DynamicBitmap::DynamicBitmap(RenderObjectPtr<RenderObject> parentPtr, uint width, uint height) : + Bitmap(parentPtr, TYPE_DYNAMICBITMAP) { + // Das BS_Bitmap konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) return; + + _initSuccess = createRenderedImage(width, height); +} + +DynamicBitmap::DynamicBitmap(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + Bitmap(parentPtr, TYPE_DYNAMICBITMAP, handle) { + _initSuccess = unpersist(reader); +} + +bool DynamicBitmap::createRenderedImage(uint width, uint height) { + // RenderedImage mit den gewünschten Maßen erstellen + bool result = false; + _image.reset(new RenderedImage(width, height, result)); + + _originalWidth = _width = width; + _originalHeight = _height = height; + + return result; +} + +DynamicBitmap::~DynamicBitmap() { +} + +uint DynamicBitmap::getPixel(int x, int y) const { + BS_ASSERT(x >= 0 && x < _width); + BS_ASSERT(y >= 0 && y < _height); + + return _image->getPixel(x, y); +} + +bool DynamicBitmap::doRender() { + // Framebufferobjekt holen + GraphicEngine *pGfx = Kernel::getInstance()->getGfx(); + BS_ASSERT(pGfx); + + // Bitmap zeichnen + bool result; + if (_scaleFactorX == 1.0f && _scaleFactorY == 1.0f) { + result = _image->blit(_absoluteX, _absoluteY, + (_flipV ? BitmapResource::FLIP_V : 0) | + (_flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, -1, -1); + } else { + result = _image->blit(_absoluteX, _absoluteY, + (_flipV ? BitmapResource::FLIP_V : 0) | + (_flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, _width, _height); + } + + return result; +} + +bool DynamicBitmap::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + return _image->setContent(pixeldata, size, offset, stride); +} + +bool DynamicBitmap::isScalingAllowed() const { + return _image->isScalingAllowed(); +} + +bool DynamicBitmap::isAlphaAllowed() const { + return _image->isAlphaAllowed(); +} + +bool DynamicBitmap::isColorModulationAllowed() const { + return _image->isColorModulationAllowed(); +} + +bool DynamicBitmap::isSetContentAllowed() const { + return true; +} + +bool DynamicBitmap::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= Bitmap::persist(writer); + + // Bilddaten werden nicht gespeichert. Dies ist auch nicht weiter von bedeutung, da BS_DynamicBitmap nur vom Videoplayer benutzt wird. + // Während ein Video abläuft kann niemals gespeichert werden. BS_DynamicBitmap kann nur der Vollständigkeit halber persistiert werden. + BS_LOG_WARNINGLN("Persisting a BS_DynamicBitmap. Bitmap content is not persisted."); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +bool DynamicBitmap::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= Bitmap::unpersist(reader); + + // Ein RenderedImage mit den gespeicherten Maßen erstellen. + result &= createRenderedImage(_width, _height); + + // Bilddaten werden nicht gespeichert (s.o.). + BS_LOG_WARNINGLN("Unpersisting a BS_DynamicBitmap. Bitmap contents are missing."); + + // Bild mit durchsichtigen Bilddaten initialisieren. + byte *transparentImageData = (byte *)calloc(_width * _height * 4, 1); + _image->setContent(transparentImageData, _width * _height); + free(transparentImageData); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/dynamicbitmap.h b/engines/sword25/gfx/dynamicbitmap.h new file mode 100644 index 0000000000..1737bdf5fc --- /dev/null +++ b/engines/sword25/gfx/dynamicbitmap.h @@ -0,0 +1,78 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_DYNAMIC_BITMAP_H +#define SWORD25_DYNAMIC_BITMAP_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/bitmap.h" +#include "sword25/gfx/image/renderedimage.h" + +#include "common/ptr.h" + +namespace Sword25 { + +class DynamicBitmap : public Bitmap { + friend class RenderObject; + +public: + virtual ~DynamicBitmap(); + + virtual uint getPixel(int x, int y) const; + + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride); + + virtual bool isScalingAllowed() const; + virtual bool isAlphaAllowed() const; + virtual bool isColorModulationAllowed() const; + virtual bool isSetContentAllowed() const; + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + virtual bool doRender(); + +private: + DynamicBitmap(RenderObjectPtr<RenderObject> parentPtr, uint width, uint height); + DynamicBitmap(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + + bool createRenderedImage(uint width, uint height); + + Common::ScopedPtr<RenderedImage> _image; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/fontresource.cpp b/engines/sword25/gfx/fontresource.cpp new file mode 100644 index 0000000000..dbb9c67fe5 --- /dev/null +++ b/engines/sword25/gfx/fontresource.cpp @@ -0,0 +1,138 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "FONTRESOURCE" + +#include "sword25/kernel/kernel.h" +#include "sword25/package/packagemanager.h" + +#include "sword25/gfx/fontresource.h" + +namespace Sword25 { + +enum { + DEFAULT_LINEHEIGHT = 20, + DEFAULT_GAPWIDTH = 1 +}; + +FontResource::FontResource(Kernel *pKernel, const Common::String &fileName) : + _pKernel(pKernel), + _valid(false), + Resource(fileName, Resource::TYPE_FONT), + Common::XMLParser() { + + // Get a pointer to the package manager + BS_ASSERT(_pKernel); + PackageManager *pPackage = _pKernel->getPackage(); + BS_ASSERT(pPackage); + + // Load the contents of the file + uint fileSize; + char *xmlData = pPackage->getXmlFile(getFileName(), &fileSize); + if (!xmlData) { + BS_LOG_ERRORLN("Could not read \"%s\".", getFileName().c_str()); + return; + } + + // Parse the contents + if (!loadBuffer((const byte *)xmlData, fileSize)) + return; + + _valid = parse(); + close(); + free(xmlData); +} + +bool FontResource::parserCallback_font(ParserNode *node) { + // Get the attributes of the font + Common::String bitmapFilename = node->values["bitmap"]; + + if (!parseIntegerKey(node->values["lineheight"], 1, &_lineHeight)) { + BS_LOG_WARNINGLN("Illegal or missing lineheight attribute in <font> tag in \"%s\". Assuming default (\"%d\").", + getFileName().c_str(), DEFAULT_LINEHEIGHT); + _lineHeight = DEFAULT_LINEHEIGHT; + } + + if (!parseIntegerKey(node->values["gap"], 1, &_gapWidth)) { + BS_LOG_WARNINGLN("Illegal or missing gap attribute in <font> tag in \"%s\". Assuming default (\"%d\").", + getFileName().c_str(), DEFAULT_GAPWIDTH); + _gapWidth = DEFAULT_GAPWIDTH; + } + + // Get a reference to the package manager + BS_ASSERT(_pKernel); + PackageManager *pPackage = _pKernel->getPackage(); + BS_ASSERT(pPackage); + + // Get the full path and filename for the bitmap resource + _bitmapFileName = pPackage->getAbsolutePath(bitmapFilename); + if (_bitmapFileName == "") { + BS_LOG_ERRORLN("Image file \"%s\" was specified in <font> tag of \"%s\" but could not be found.", + _bitmapFileName.c_str(), getFileName().c_str()); + } + + // Pre-cache the resource + if (!_pKernel->getResourceManager()->precacheResource(_bitmapFileName)) { + BS_LOG_ERRORLN("Could not precache \"%s\".", _bitmapFileName.c_str()); + } + + return true; +} + +bool FontResource::parserCallback_character(ParserNode *node) { + // Get the attributes of the character + int charCode, top, left, right, bottom; + + if (!parseIntegerKey(node->values["code"], 1, &charCode) || (charCode < 0) || (charCode >= 256)) { + return parserError("Illegal or missing code attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + + if (!parseIntegerKey(node->values["top"], 1, &top) || (top < 0)) { + return parserError("Illegal or missing top attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + if (!parseIntegerKey(node->values["left"], 1, &left) || (left < 0)) { + return parserError("Illegal or missing left attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + if (!parseIntegerKey(node->values["right"], 1, &right) || (right < 0)) { + return parserError("Illegal or missing right attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + if (!parseIntegerKey(node->values["bottom"], 1, &bottom) || (bottom < 0)) { + return parserError("Illegal or missing bottom attribute in <character> tag in \"%s\".", getFileName().c_str()); + } + + this->_characterRects[charCode] = Common::Rect(left, top, right, bottom); + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/fontresource.h b/engines/sword25/gfx/fontresource.h new file mode 100644 index 0000000000..19c44d0ade --- /dev/null +++ b/engines/sword25/gfx/fontresource.h @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_FONTRESOURCE_H +#define SWORD25_FONTRESOURCE_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "common/xmlparser.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/resource.h" + +namespace Sword25 { + +class Kernel; + +class FontResource : public Resource, Common::XMLParser { +public: + /** + @brief Erzeugt eine neues Exemplar von BS_FontResource + @param pKernel ein Pointer auf den Kernel + @param FileName der Dateiname der zu ladenen Resource + @remark Wenn der Konstruktor erfolgreich ausgeführt werden konnte gibt die Methode IsValid true zurück. + */ + FontResource(Kernel *pKernel, const Common::String &fileName); + + /** + @brief Gibt true zurück, wenn das Objekt korrekt initialisiert wurde. + + Diese Methode kann dazu benutzt werden um festzustellen, ob der Konstruktor erfolgreich ausgeführt wurde. + */ + bool isValid() const { + return _valid; + } + + /** + @brief Gibt die Zeilenhöhe des Fonts in Pixeln zurück. + + Die Zeilenhöhe ist der Wert, der zur Y-Koordinate addiert wird, wenn ein Zeilenumbruch auftritt. + */ + int getLineHeight() const { + return _lineHeight; + } + + /** + @brief Gibt den Buchstabenabstand der Fonts in Pixeln zurück. + + Der Buchstabenabstand ist der Wert, der zwischen zwei Buchstaben freigelassen wird. + */ + int getGapWidth() const { + return _gapWidth; + } + + /** + @brief Gibt das Bounding-Rect eines Zeichens auf der Charactermap zurück. + @param Character der ASCII-Code des Zeichens + @return Das Bounding-Rect des übergebenen Zeichens auf der Charactermap. + */ + const Common::Rect &getCharacterRect(int character) const { + BS_ASSERT(character >= 0 && character < 256); + return _characterRects[character]; + } + + /** + @brief Gibt den Dateinamen der Charactermap zurück. + */ + const Common::String &getCharactermapFileName() const { + return _bitmapFileName; + } + +private: + Kernel *_pKernel; + bool _valid; + Common::String _bitmapFileName; + int _lineHeight; + int _gapWidth; + Common::Rect _characterRects[256]; + + // Parser + CUSTOM_XML_PARSER(FontResource) { + XML_KEY(font) + XML_PROP(bitmap, true) + XML_PROP(lineheight, false) + XML_PROP(gap, false) + + XML_KEY(character) + XML_PROP(code, true) + XML_PROP(left, true) + XML_PROP(top, true) + XML_PROP(right, true) + XML_PROP(bottom, true) + KEY_END() + KEY_END() + } PARSER_END() + + // Parser callback methods + bool parserCallback_font(ParserNode *node); + bool parserCallback_character(ParserNode *node); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/framecounter.cpp b/engines/sword25/gfx/framecounter.cpp new file mode 100644 index 0000000000..07415cc2dc --- /dev/null +++ b/engines/sword25/gfx/framecounter.cpp @@ -0,0 +1,68 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "common/system.h" +#include "sword25/gfx/framecounter.h" + +namespace Sword25 { + +Framecounter::Framecounter(int updateFrequency) : + _FPS(0), + _FPSCount(0), + _lastUpdateTime(-1) { + setUpdateFrequency(updateFrequency); +} + +void Framecounter::update() { + // Aktuellen Systemtimerstand auslesen + uint64 timer = g_system->getMillis() * 1000; + + // Falls m_LastUpdateTime == -1 ist, wird der Frame-Counter zum ersten Mal aufgerufen und der aktuelle Systemtimer als erster + // Messzeitpunkt genommen. + if (_lastUpdateTime == -1) + _lastUpdateTime = timer; + else { + // Die Anzahl der Frames im aktuellen Messzeitraum wird erhöht. + _FPSCount++; + + // Falls der Messzeitraum verstrichen ist, wird die durchschnittliche Framerate berechnet und ein neuer Messzeitraum begonnen. + if (timer - _lastUpdateTime >= _updateDelay) { + _FPS = static_cast<int>((1000000 * (uint64)_FPSCount) / (timer - _lastUpdateTime)); + _lastUpdateTime = timer; + _FPSCount = 0; + } + } +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/framecounter.h b/engines/sword25/gfx/framecounter.h new file mode 100644 index 0000000000..994950573f --- /dev/null +++ b/engines/sword25/gfx/framecounter.h @@ -0,0 +1,99 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_FRAMECOUNTER_H +#define SWORD25_FRAMECOUNTER_H + +// Includes +#include "sword25/kernel/common.h" + +namespace Sword25 { + +/** + * A simple class that implements a frame counter + */ +class Framecounter { +private: + + // TODO: This class should be rewritten based on Audio::Timestamp, + // which provides higher accuracy and avoids using 64 bit data types. + typedef unsigned long long uint64; + typedef signed long long int64; + + enum { + DEFAULT_UPDATE_FREQUENCY = 10 + }; + +public: + /** + * Creates a new BS_Framecounter object + * @param UpdateFrequency Specifies how often the frame counter should be updated in a sceond. + * The default value is 10. + */ + Framecounter(int updateFrequency = DEFAULT_UPDATE_FREQUENCY); + + /** + * Determines how often the frame counter should be updated in a second. + * @param UpdateFrequency Specifies how often the frame counter should be updated in a second. + */ + inline void setUpdateFrequency(int updateFrequency); + + /** + * This method must be called once per frame. + */ + void update(); + + /** + * Returns the current FPS value. + */ + int getFPS() const { + return _FPS; + } + +private: + int _FPS; + int _FPSCount; + int64 _lastUpdateTime; + uint64 _updateDelay; +}; + +// Inlines +void Framecounter::setUpdateFrequency(int updateFrequency) { + // Frequency in time (converted to microseconds) + _updateDelay = 1000000 / updateFrequency; +} + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/graphicengine.cpp b/engines/sword25/gfx/graphicengine.cpp new file mode 100644 index 0000000000..f629993abf --- /dev/null +++ b/engines/sword25/gfx/graphicengine.cpp @@ -0,0 +1,494 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "GRAPHICENGINE" + +#include "common/system.h" + +#include "sword25/gfx/bitmapresource.h" +#include "sword25/gfx/animationresource.h" +#include "sword25/gfx/fontresource.h" +#include "sword25/gfx/panel.h" +#include "sword25/gfx/renderobjectmanager.h" +#include "sword25/gfx/screenshot.h" +#include "sword25/gfx/image/renderedimage.h" +#include "sword25/gfx/image/swimage.h" +#include "sword25/gfx/image/vectorimage.h" +#include "sword25/package/packagemanager.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" + + +#include "sword25/gfx/graphicengine.h" + +#include "sword25/util/lua/lua.h" +#include "sword25/util/lua/lauxlib.h" +enum { + BIT_DEPTH = 32, + BACKBUFFER_COUNT = 1 +}; + + +namespace Sword25 { + +static const uint FRAMETIME_SAMPLE_COUNT = 5; // Anzahl der Framezeiten über die, die Framezeit gemittelt wird + +GraphicEngine::GraphicEngine(Kernel *pKernel) : + _width(0), + _height(0), + _bitDepth(0), + _windowed(0), + _lastTimeStamp((uint) -1), // max. BS_INT64 um beim ersten Aufruf von _UpdateLastFrameDuration() einen Reset zu erzwingen + _lastFrameDuration(0), + _timerActive(true), + _frameTimeSampleSlot(0), + _repaintedPixels(0), + _thumbnail(NULL), + ResourceService(pKernel) { + _frameTimeSamples.resize(FRAMETIME_SAMPLE_COUNT); + + if (!registerScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); +} + +GraphicEngine::~GraphicEngine() { + unregisterScriptBindings(); + _backSurface.free(); + _frameBuffer.free(); + delete _thumbnail; +} + +bool GraphicEngine::init(int width, int height, int bitDepth, int backbufferCount, bool isWindowed_) { + // Warnung ausgeben, wenn eine nicht unterstützte Bittiefe gewählt wurde. + if (bitDepth != BIT_DEPTH) { + BS_LOG_WARNINGLN("Can't use a bit depth of %d (not supported). Falling back to %d.", bitDepth, BIT_DEPTH); + _bitDepth = BIT_DEPTH; + } + + // Warnung ausgeben, wenn nicht genau ein Backbuffer gewählt wurde. + if (backbufferCount != BACKBUFFER_COUNT) { + BS_LOG_WARNINGLN("Can't use %d backbuffers (not supported). Falling back to %d.", backbufferCount, BACKBUFFER_COUNT); + backbufferCount = BACKBUFFER_COUNT; + } + + // Parameter in lokale Variablen kopieren + _width = width; + _height = height; + _bitDepth = bitDepth; + _windowed = isWindowed_; + _screenRect.left = 0; + _screenRect.top = 0; + _screenRect.right = _width; + _screenRect.bottom = _height; + + _backSurface.create(width, height, 4); + _frameBuffer.create(width, height, 4); + + // Standardmäßig ist Vsync an. + setVsync(true); + + // Layer-Manager initialisieren. + _renderObjectManagerPtr.reset(new RenderObjectManager(width, height, backbufferCount + 1)); + + // Hauptpanel erstellen + _mainPanelPtr = _renderObjectManagerPtr->getTreeRoot()->addPanel(width, height, BS_ARGB(0, 0, 0, 0)); + if (!_mainPanelPtr.isValid()) + return false; + _mainPanelPtr->setVisible(true); + + return true; +} + +bool GraphicEngine::startFrame(bool updateAll) { + // Berechnen, wie viel Zeit seit dem letzten Frame vergangen ist. + // Dieser Wert kann über GetLastFrameDuration() von Modulen abgefragt werden, die zeitabhängig arbeiten. + updateLastFrameDuration(); + + // Den Layer-Manager auf den nächsten Frame vorbereiten + _renderObjectManagerPtr->startFrame(); + + return true; +} + +bool GraphicEngine::endFrame() { + // Scene zeichnen + _renderObjectManagerPtr->render(); + + // FIXME: The frame buffer surface is only used as the base for creating thumbnails when saving the + // game, since the _backSurface is blanked. Currently I'm doing a slightly hacky check and only + // copying the back surface if line 50 (the first line after the interface area) is non-blank + if (READ_LE_UINT32((byte *)_backSurface.pixels + (_backSurface.pitch * 50)) & 0xffffff) { + // Make a copy of the current frame into the frame buffer + Common::copy((byte *)_backSurface.pixels, (byte *)_backSurface.pixels + + (_backSurface.pitch * _backSurface.h), (byte *)_frameBuffer.pixels); + } + + g_system->updateScreen(); + + // Debug-Lines zeichnen + if (!_debugLines.empty()) { +#if 0 + glEnable(GL_LINE_SMOOTH); + glBegin(GL_LINES); + + Common::Array<DebugLine>::const_iterator iter = m_DebugLines.begin(); + for (; iter != m_DebugLines.end(); ++iter) { + const uint &Color = (*iter).Color; + const BS_Vertex &Start = (*iter).Start; + const BS_Vertex &End = (*iter).End; + + glColor4ub((Color >> 16) & 0xff, (Color >> 8) & 0xff, Color & 0xff, Color >> 24); + glVertex2d(Start.X, Start.Y); + glVertex2d(End.X, End.Y); + } + + glEnd(); + glDisable(GL_LINE_SMOOTH); +#endif + + warning("STUB: Drawing debug lines"); + + _debugLines.clear(); + } + + // Framecounter aktualisieren + _FPSCounter.update(); + + return true; +} + +RenderObjectPtr<Panel> GraphicEngine::getMainPanel() { + return _mainPanelPtr; +} + +void GraphicEngine::setVsync(bool vsync) { + warning("STUB: SetVsync(%d)", vsync); +} + +bool GraphicEngine::getVsync() const { + warning("STUB: getVsync()"); + + return true; +} + +bool GraphicEngine::fill(const Common::Rect *fillRectPtr, uint color) { + Common::Rect rect(_width - 1, _height - 1); + + int ca = (color >> 24) & 0xff; + + if (ca == 0) + return true; + + int cr = (color >> 16) & 0xff; + int cg = (color >> 8) & 0xff; + int cb = (color >> 0) & 0xff; + + if (fillRectPtr) { + rect = *fillRectPtr; + } + + if (rect.width() > 0 && rect.height() > 0) { + if (ca == 0xff) { + _backSurface.fillRect(rect, color); + } else { + byte *outo = (byte *)_backSurface.getBasePtr(rect.left, rect.top); + byte *out; + + for (int i = rect.top; i < rect.bottom; i++) { + out = outo; + for (int j = rect.left; j < rect.right; j++) { + *out += (byte)(((cb - *out) * ca) >> 8); + out++; + *out += (byte)(((cg - *out) * ca) >> 8); + out++; + *out += (byte)(((cr - *out) * ca) >> 8); + out++; + *out = 255; + out++; + } + + outo += _backSurface.pitch; + } + } + + g_system->copyRectToScreen((byte *)_backSurface.getBasePtr(rect.left, rect.top), _backSurface.pitch, rect.left, rect.top, rect.width(), rect.height()); + } + + return true; +} + +Graphics::Surface *GraphicEngine::getScreenshot() { + return &_frameBuffer; +} + +// ----------------------------------------------------------------------------- +// RESOURCE MANAGING +// ----------------------------------------------------------------------------- + +Resource *GraphicEngine::loadResource(const Common::String &filename) { + BS_ASSERT(canLoadResource(filename)); + + // Load image for "software buffer" (FIXME: Whatever that means?) + if (filename.hasSuffix("_s.png")) { + bool result = false; + SWImage *pImage = new SWImage(filename, result); + if (!result) { + delete pImage; + return 0; + } + + BitmapResource *pResource = new BitmapResource(filename, pImage); + if (!pResource->isValid()) { + delete pResource; + return 0; + } + + return pResource; + } + + // Load sprite image + if (filename.hasSuffix(".png") || filename.hasSuffix(".b25s")) { + bool result = false; + RenderedImage *pImage = new RenderedImage(filename, result); + if (!result) { + delete pImage; + return 0; + } + + BitmapResource *pResource = new BitmapResource(filename, pImage); + if (!pResource->isValid()) { + delete pResource; + return 0; + } + + return pResource; + } + + + // Load vector graphics + if (filename.hasSuffix(".swf")) { + debug(2, "VectorImage: %s", filename.c_str()); + + // Pointer auf Package-Manager holen + PackageManager *pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(pPackage); + + // Datei laden + byte *pFileData; + uint fileSize; + if (!(pFileData = static_cast<byte *>(pPackage->getFile(filename, &fileSize)))) { + BS_LOG_ERRORLN("File \"%s\" could not be loaded.", filename.c_str()); + return 0; + } + + bool result = false; + VectorImage *pImage = new VectorImage(pFileData, fileSize, result, filename); + if (!result) { + delete pImage; + delete[] pFileData; + return 0; + } + + BitmapResource *pResource = new BitmapResource(filename, pImage); + if (!pResource->isValid()) { + delete pResource; + delete[] pFileData; + return 0; + } + + delete[] pFileData; + return pResource; + } + + // Load animation + if (filename.hasSuffix("_ani.xml")) { + AnimationResource *pResource = new AnimationResource(filename); + if (pResource->isValid()) + return pResource; + else { + delete pResource; + return 0; + } + } + + // Load font + if (filename.hasSuffix("_fnt.xml")) { + FontResource *pResource = new FontResource(Kernel::getInstance(), filename); + if (pResource->isValid()) + return pResource; + else { + delete pResource; + return 0; + } + } + + BS_LOG_ERRORLN("Service cannot load \"%s\".", filename.c_str()); + return 0; +} + +// ----------------------------------------------------------------------------- + +bool GraphicEngine::canLoadResource(const Common::String &filename) { + return filename.hasSuffix(".png") || + filename.hasSuffix("_ani.xml") || + filename.hasSuffix("_fnt.xml") || + filename.hasSuffix(".swf") || + filename.hasSuffix(".b25s"); +} + + +// ----------------------------------------------------------------------------- +// DEBUGGING +// ----------------------------------------------------------------------------- + +void GraphicEngine::drawDebugLine(const Vertex &start, const Vertex &end, uint color) { + _debugLines.push_back(DebugLine(start, end, color)); +} + +void GraphicEngine::updateLastFrameDuration() { + // Record current time + const uint currentTime = Kernel::getInstance()->getMilliTicks(); + + // Compute the elapsed time since the last frame and prevent too big ( > 250 msecs) time jumps. + // These can occur when loading save states, during debugging or due to hardware inaccuracies. + _frameTimeSamples[_frameTimeSampleSlot] = static_cast<uint>(currentTime - _lastTimeStamp); + if (_frameTimeSamples[_frameTimeSampleSlot] > 250000) + _frameTimeSamples[_frameTimeSampleSlot] = 250000; + _frameTimeSampleSlot = (_frameTimeSampleSlot + 1) % FRAMETIME_SAMPLE_COUNT; + + // Compute the average frame duration over multiple frames to eliminate outliers. + Common::Array<uint>::const_iterator it = _frameTimeSamples.begin(); + uint sum = *it; + for (it++; it != _frameTimeSamples.end(); it++) + sum += *it; + _lastFrameDuration = sum * 1000 / FRAMETIME_SAMPLE_COUNT; + + // Update m_LastTimeStamp with the current frame's timestamp + _lastTimeStamp = currentTime; +} + +bool GraphicEngine::saveThumbnailScreenshot(const Common::String &filename) { + // Note: In ScumMVM, rather than saivng the thumbnail to a file, we store it in memory + // until needed when creating savegame files + delete _thumbnail; + _thumbnail = Screenshot::createThumbnail(&_frameBuffer); + return true; +} + +void GraphicEngine::ARGBColorToLuaColor(lua_State *L, uint color) { + lua_Number components[4] = { + (color >> 16) & 0xff, // Rot + (color >> 8) & 0xff, // Grün + color & 0xff, // Blau + color >> 24, // Alpha + }; + + lua_newtable(L); + + for (uint i = 1; i <= 4; i++) { + lua_pushnumber(L, i); + lua_pushnumber(L, components[i - 1]); + lua_settable(L, -3); + } +} + +uint GraphicEngine::luaColorToARGBColor(lua_State *L, int stackIndex) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // Sicherstellen, dass wir wirklich eine Tabelle betrachten + luaL_checktype(L, stackIndex, LUA_TTABLE); + // Größe der Tabelle auslesen + uint n = luaL_getn(L, stackIndex); + // RGB oder RGBA Farben werden unterstützt und sonst keine + if (n != 3 && n != 4) + luaL_argcheck(L, 0, stackIndex, "at least 3 of the 4 color components have to be specified"); + + // Red color component reading + lua_rawgeti(L, stackIndex, 1); + uint red = static_cast<uint>(lua_tonumber(L, -1)); + if (!lua_isnumber(L, -1) || red >= 256) + luaL_argcheck(L, 0, stackIndex, "red color component must be an integer between 0 and 255"); + lua_pop(L, 1); + + // Green color component reading + lua_rawgeti(L, stackIndex, 2); + uint green = static_cast<uint>(lua_tonumber(L, -1)); + if (!lua_isnumber(L, -1) || green >= 256) + luaL_argcheck(L, 0, stackIndex, "green color component must be an integer between 0 and 255"); + lua_pop(L, 1); + + // Blue color component reading + lua_rawgeti(L, stackIndex, 3); + uint blue = static_cast<uint>(lua_tonumber(L, -1)); + if (!lua_isnumber(L, -1) || blue >= 256) + luaL_argcheck(L, 0, stackIndex, "blue color component must be an integer between 0 and 255"); + lua_pop(L, 1); + + // Alpha color component reading + uint alpha = 0xff; + if (n == 4) { + lua_rawgeti(L, stackIndex, 4); + alpha = static_cast<uint>(lua_tonumber(L, -1)); + if (!lua_isnumber(L, -1) || alpha >= 256) + luaL_argcheck(L, 0, stackIndex, "alpha color component must be an integer between 0 and 255"); + lua_pop(L, 1); + } + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return (alpha << 24) | (red << 16) | (green << 8) | blue; +} + +bool GraphicEngine::persist(OutputPersistenceBlock &writer) { + writer.write(_timerActive); + + bool result = _renderObjectManagerPtr->persist(writer); + + return result; +} + +bool GraphicEngine::unpersist(InputPersistenceBlock &reader) { + reader.read(_timerActive); + _renderObjectManagerPtr->unpersist(reader); + + return reader.isGood(); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/graphicengine.h b/engines/sword25/gfx/graphicengine.h new file mode 100644 index 0000000000..e1ae0d2d62 --- /dev/null +++ b/engines/sword25/gfx/graphicengine.h @@ -0,0 +1,392 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + * GraphicEngine + * ---------------- + * This the graphics engine interface. + * + * Autor: Malte Thiesen + */ + +#ifndef SWORD25_GRAPHICENGINE_H +#define SWORD25_GRAPHICENGINE_H + +// Includes +#include "common/array.h" +#include "common/rect.h" +#include "common/ptr.h" +#include "common/str.h" +#include "graphics/surface.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/resservice.h" +#include "sword25/kernel/persistable.h" +#include "sword25/gfx/framecounter.h" +#include "sword25/gfx/renderobjectptr.h" +#include "sword25/math/vertex.h" + +namespace Sword25 { + +class Kernel; +class Image; +class Panel; +class Screenshot; +class RenderObjectManager; + +typedef uint BS_COLOR; + +#define BS_RGB(R,G,B) (0xFF000000 | ((R) << 16) | ((G) << 8) | (B)) +#define BS_ARGB(A,R,G,B) (((A) << 24) | ((R) << 16) | ((G) << 8) | (B)) + +/** + * This is the graphics engine. Unlike the original code, this is not + * an interface that needs to be subclassed, but rather already contains + * all required functionality. + */ +class GraphicEngine : public ResourceService, public Persistable { +public: + // Enums + // ----- + + // Colour formats + // + /** + * The colour format used by the engine + */ + enum COLOR_FORMATS { + /// Undefined/unknown colour format + CF_UNKNOWN = 0, + /** + * 24-bit colour format (R8G8B8) + */ + CF_RGB24, + /** + * 32-bit colour format (A8R8G8B8) (little endian) + */ + CF_ARGB32, + /** + 32-bit colour format (A8B8G8R8) (little endian) + */ + CF_ABGR32 + }; + + // Constructor + // ----------- + GraphicEngine(Kernel *pKernel); + ~GraphicEngine(); + + // Interface + // --------- + + /** + * Initialises the graphics engine and sets the screen mode. Returns + * true if initialisation failed. + * @note This method should be called immediately after the + * initialisation of all services. + * + * @param Height The height of the output buffer in pixels. The default value is 600 + * @param BitDepth The bit depth of the desired output buffer in bits. The default value is 16 + * @param BackbufferCount The number of back buffers to be created. The default value is 2 + * @param Windowed Indicates whether the engine is to run in windowed mode. + */ + bool init(int width = 800, int height = 600, int bitDepth = 16, int backbufferCount = 2, bool windowed = false); + + /** + * Begins rendering a new frame. + * Notes: This method must be called at the beginning of the main loop, before any rendering methods are used. + * Notes: Implementations of this method must call _UpdateLastFrameDuration() + * @param UpdateAll Specifies whether the renderer should redraw everything on the next frame. + * This feature can be useful if the renderer with Dirty Rectangles works, but sometimes the client may + */ + bool startFrame(bool updateAll = false); + + /** + * Ends the rendering of a frame and draws it on the screen. + * + * This method must be at the end of the main loop. After this call, no further Render method may be called. + * This should only be called once for a given previous call to #StartFrame. + */ + bool endFrame(); + + // Debug methods + + /** + * Draws a line in the frame buffer + * + * This method must be called between calls to StartFrame() and EndFrame(), and is intended only for debugging + * purposes. The line will only appear for a single frame. If the line is to be shown permanently, it must be + * called for every frame. + * @param Start The starting point of the line + * @param End The ending point of the line + * @param Color The colour of the line. The default is BS_RGB (255,255,255) (White) + */ + void drawDebugLine(const Vertex &start, const Vertex &end, uint color = BS_RGB(255, 255, 255)); + + /** + * Creates a thumbnail with the dimensions of 200x125. This will not include the top and bottom of the screen.. + * the interface boards the the image as a 16th of it's original size. + * Notes: This method should only be called after a call to EndFrame(), and before the next call to StartFrame(). + * The frame buffer must have a resolution of 800x600. + * @param Filename The filename for the screenshot + */ + bool saveThumbnailScreenshot(const Common::String &filename); + + /** + * Reads the current contents of the frame buffer + * Notes: This method is for creating screenshots. It is not very optimised. It should only be called + * after a call to EndFrame(), and before the next call to StartFrame(). + * @param Width Returns the width of the frame buffer + * @param Height Returns the height of the frame buffer + * @param Data Returns the raw data of the frame buffer as an array of 32-bit colour values. + */ + Graphics::Surface *getScreenshot(); + + + RenderObjectPtr<Panel> getMainPanel(); + + /** + * Specifies the time (in microseconds) since the last frame has passed + */ + int getLastFrameDurationMicro() const { + if (!_timerActive) + return 0; + return _lastFrameDuration; + } + + /** + * Specifies the time (in microseconds) the previous frame took + */ + float getLastFrameDuration() const { + if (!_timerActive) + return 0; + return static_cast<float>(_lastFrameDuration) / 1000000.0f; + } + + void stopMainTimer() { + _timerActive = false; + } + + void resumeMainTimer() { + _timerActive = true; + } + + float getSecondaryFrameDuration() const { + return static_cast<float>(_lastFrameDuration) / 1000000.0f; + } + + // Accessor methods + + /** + * Returns the width of the output buffer in pixels + */ + int getDisplayWidth() const { + return _width; + } + + /** + * Returns the height of the output buffer in pixels + */ + int getDisplayHeight() const { + return _height; + } + + /** + * Returns the bounding box of the output buffer: (0, 0, Width, Height) + */ + Common::Rect &getDisplayRect() { + return _screenRect; + } + + /** + * Returns the bit depth of the output buffer + */ + int getBitDepth() { + return _bitDepth; + } + + /** + * Determines whether the frame buffer change is to be synchronised with Vsync. This is turned on by default. + * Notes: In windowed mode, this setting has no effect. + * @param Vsync Indicates whether the frame buffer changes are to be synchronised with Vsync. + */ + void setVsync(bool vsync); + + /** + * Returns true if V-Sync is on. + * Notes: In windowed mode, this setting has no effect. + */ + bool getVsync() const; + + /** + * Returns true if the engine is running in Windowed mode. + */ + bool isWindowed() { + return _windowed; + } + + /** + * Fills a rectangular area of the frame buffer with a colour. + * Notes: It is possible to create transparent rectangles by passing a colour with an Alpha value of 255. + * @param FillRectPtr Pointer to a Common::Rect, which specifies the section of the frame buffer to be filled. + * If the rectangle falls partly off-screen, then it is automatically trimmed. + * If a NULL value is passed, then the entire image is to be filled. + * @param Color The 32-bit colour with which the area is to be filled. The default is BS_RGB(0, 0, 0) (black) + * @note FIf the rectangle is not completely inside the screen, it is automatically clipped. + */ + bool fill(const Common::Rect *fillRectPtr = 0, uint color = BS_RGB(0, 0, 0)); + + // Debugging Methods + + int getFPSCount() const { + return _FPSCounter.getFPS(); + } + int getRepaintedPixels() const { + return _repaintedPixels; + } + + Graphics::Surface _backSurface; + Graphics::Surface *getSurface() { return &_backSurface; } + + Graphics::Surface _frameBuffer; + Graphics::Surface *getFrameBuffer() { return &_frameBuffer; } + + Common::MemoryReadStream *_thumbnail; + Common::MemoryReadStream *getThumbnail() { return _thumbnail; } + + // Access methods + + /** + * Returns the size of a pixel entry in bytes for a particular colour format + * @param ColorFormat The desired colour format. The parameter must be of type COLOR_FORMATS + * @return Returns the size of a pixel in bytes. If the colour format is unknown, -1 is returned. + */ + static int getPixelSize(GraphicEngine::COLOR_FORMATS colorFormat) { + switch (colorFormat) { + case GraphicEngine::CF_ARGB32: + return 4; + default: + return -1; + } + } + + /** + * Calculates the length of an image line in bytes, depending on a given colour format. + * @param ColorFormat The colour format + * @param Width The width of the line in pixels + * @return Reflects the length of the line in bytes. If the colour format is + * unknown, -1 is returned + */ + static int calcPitch(GraphicEngine::COLOR_FORMATS colorFormat, int width) { + switch (colorFormat) { + case GraphicEngine::CF_ARGB32: + return width * 4; + + default: + BS_ASSERT(false); + } + + return -1; + } + + // Resource-Managing Methods + // -------------------------- + virtual Resource *loadResource(const Common::String &fileName); + virtual bool canLoadResource(const Common::String &fileName); + + // Persistence Methods + // ------------------- + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + + static void ARGBColorToLuaColor(lua_State *L, uint color); + static uint luaColorToARGBColor(lua_State *L, int stackIndex); + +protected: + + // Display Variables + // ----------------- + int _width; + int _height; + Common::Rect _screenRect; + int _bitDepth; + bool _windowed; + + // Debugging Variables + // ------------------- + Framecounter _FPSCounter; + + uint _repaintedPixels; + + /** + * Calculates the time since the last frame beginning has passed. + */ + void updateLastFrameDuration(); + +private: + bool registerScriptBindings(); + void unregisterScriptBindings(); + + // LastFrameDuration Variables + // --------------------------- + uint _lastTimeStamp; + uint _lastFrameDuration; + bool _timerActive; + Common::Array<uint> _frameTimeSamples; + uint _frameTimeSampleSlot; + +private: + byte *_backBuffer; + + RenderObjectPtr<Panel> _mainPanelPtr; + + Common::ScopedPtr<RenderObjectManager> _renderObjectManagerPtr; + + struct DebugLine { + DebugLine(const Vertex &start, const Vertex &end, uint color) : + _start(start), + _end(end), + _color(color) {} + DebugLine() {} + + Vertex _start; + Vertex _end; + uint _color; + }; + + Common::Array<DebugLine> _debugLines; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/graphicengine_script.cpp b/engines/sword25/gfx/graphicengine_script.cpp new file mode 100644 index 0000000000..0814a23871 --- /dev/null +++ b/engines/sword25/gfx/graphicengine_script.cpp @@ -0,0 +1,1305 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/common.h" +#include "sword25/kernel/kernel.h" +#include "sword25/script/script.h" +#include "sword25/script/luabindhelper.h" +#include "sword25/script/luacallback.h" +#include "sword25/math/vertex.h" + +#include "sword25/gfx/graphicengine.h" +#include "sword25/gfx/renderobject.h" +#include "sword25/gfx/bitmap.h" +#include "sword25/gfx/animation.h" +#include "sword25/gfx/panel.h" +#include "sword25/gfx/text.h" +#include "sword25/gfx/animationtemplate.h" +#include "sword25/gfx/animationtemplateregistry.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "GRAPHICENGINE" + +static bool animationDeleteCallback(uint Data); +static bool animationActionCallback(uint Data); +static bool animationLoopPointCallback(uint Data); + +namespace { +class ActionCallback : public LuaCallback { +public: + ActionCallback(lua_State *L) : LuaCallback(L) {} + + Common::String Action; + +protected: + virtual int PreFunctionInvokation(lua_State *L) { + lua_pushstring(L, Action.c_str()); + return 1; + } +}; + +static LuaCallback *loopPointCallbackPtr = 0; // FIXME: should be turned into GraphicEngine member var +static ActionCallback *actionCallbackPtr = 0; // FIXME: should be turned into GraphicEngine member var +} + +// Die Strings werden als #defines definiert um Stringkomposition zur Compilezeit zu ermöglichen. +#define RENDEROBJECT_CLASS_NAME "Gfx.RenderObject" +#define BITMAP_CLASS_NAME "Gfx.Bitmap" +#define PANEL_CLASS_NAME "Gfx.Panel" +#define TEXT_CLASS_NAME "Gfx.Text" +#define ANIMATION_CLASS_NAME "Gfx.Animation" +#define ANIMATION_TEMPLATE_CLASS_NAME "Gfx.AnimationTemplate" +static const char *GFX_LIBRARY_NAME = "Gfx"; + +// Wie luaL_checkudata, nur ohne dass kein Fehler erzeugt wird. +static void *my_checkudata(lua_State *L, int ud, const char *tname) { + int top = lua_gettop(L); + + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + // lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ + LuaBindhelper::getMetatable(L, tname); + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ + lua_settop(L, top); + return p; + } + } + } + + lua_settop(L, top); + return NULL; +} + +static void newUintUserData(lua_State *L, uint value) { + void *userData = lua_newuserdata(L, sizeof(value)); + memcpy(userData, &value, sizeof(value)); +} + +static AnimationTemplate *checkAnimationTemplate(lua_State *L, int idx = 1) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.AnimationTemplate + uint animationTemplateHandle; + if ((animationTemplateHandle = *reinterpret_cast<uint *>(my_checkudata(L, idx, ANIMATION_TEMPLATE_CLASS_NAME))) != 0) { + AnimationTemplate *animationTemplatePtr = AnimationTemplateRegistry::instance().resolveHandle(animationTemplateHandle); + if (!animationTemplatePtr) + luaL_error(L, "The animation template with the handle %d does no longer exist.", animationTemplateHandle); + return animationTemplatePtr; + } else { + luaL_argcheck(L, 0, idx, "'" ANIMATION_TEMPLATE_CLASS_NAME "' expected"); + return 0; + } +} + + +static int newAnimationTemplate(lua_State *L) { + uint animationTemplateHandle = AnimationTemplate::create(luaL_checkstring(L, 1)); + AnimationTemplate *animationTemplatePtr = AnimationTemplateRegistry::instance().resolveHandle(animationTemplateHandle); + if (animationTemplatePtr && animationTemplatePtr->isValid()) { + newUintUserData(L, animationTemplateHandle); + //luaL_getmetatable(L, ANIMATION_TEMPLATE_CLASS_NAME); + LuaBindhelper::getMetatable(L, ANIMATION_TEMPLATE_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + } else { + lua_pushnil(L); + } + + return 1; +} + +static int at_addFrame(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + pAT->addFrame(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int at_setFrame(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + pAT->setFrame(static_cast<int>(luaL_checknumber(L, 2)), static_cast<int>(luaL_checknumber(L, 3))); + return 0; +} + +static bool animationTypeStringToNumber(const char *typeString, Animation::ANIMATION_TYPES &result) { + if (strcmp(typeString, "jojo") == 0) { + result = Animation::AT_JOJO; + return true; + } else if (strcmp(typeString, "loop") == 0) { + result = Animation::AT_LOOP; + return true; + } else if (strcmp(typeString, "oneshot") == 0) { + result = Animation::AT_ONESHOT; + return true; + } else + return false; +} + +static int at_setAnimationType(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + Animation::ANIMATION_TYPES animationType; + if (animationTypeStringToNumber(luaL_checkstring(L, 2), animationType)) { + pAT->setAnimationType(animationType); + } else { + luaL_argcheck(L, 0, 2, "Invalid animation type"); + } + + return 0; +} + +static int at_setFPS(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + pAT->setFPS(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int at_finalize(lua_State *L) { + AnimationTemplate *pAT = checkAnimationTemplate(L); + delete pAT; + return 0; +} + +static const luaL_reg ANIMATION_TEMPLATE_METHODS[] = { + {"AddFrame", at_addFrame}, + {"SetFrame", at_setFrame}, + {"SetAnimationType", at_setAnimationType}, + {"SetFPS", at_setFPS}, + {"__gc", at_finalize}, + {0, 0} +}; + +static GraphicEngine *getGE() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + GraphicEngine *pGE = pKernel->getGfx(); + BS_ASSERT(pGE); + return pGE; +} + +static int init(lua_State *L) { + GraphicEngine *pGE = getGE(); + + switch (lua_gettop(L)) { + case 0: + lua_pushbooleancpp(L, pGE->init()); + break; + case 1: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)))); + break; + case 2: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)), static_cast<int>(luaL_checknumber(L, 2)))); + break; + case 3: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)), static_cast<int>(luaL_checknumber(L, 2)), + static_cast<int>(luaL_checknumber(L, 3)))); + break; + case 4: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)), static_cast<int>(luaL_checknumber(L, 2)), + static_cast<int>(luaL_checknumber(L, 3)), static_cast<int>(luaL_checknumber(L, 4)))); + break; + default: + lua_pushbooleancpp(L, pGE->init(static_cast<int>(luaL_checknumber(L, 1)), static_cast<int>(luaL_checknumber(L, 2)), + static_cast<int>(luaL_checknumber(L, 3)), static_cast<int>(luaL_checknumber(L, 4)), + lua_tobooleancpp(L, 5))); + } + + +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // Main-Panel zum Gfx-Modul hinzufügen + RenderObjectPtr<Panel> mainPanelPtr(getGE()->getMainPanel()); + BS_ASSERT(mainPanelPtr.isValid()); + + lua_pushstring(L, GFX_LIBRARY_NAME); + lua_gettable(L, LUA_GLOBALSINDEX); + BS_ASSERT(!lua_isnil(L, -1)); + + newUintUserData(L, mainPanelPtr->getHandle()); + BS_ASSERT(!lua_isnil(L, -1)); + // luaL_getmetatable(L, PANEL_CLASS_NAME); + LuaBindhelper::getMetatable(L, PANEL_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + + lua_pushstring(L, "MainPanel"); + lua_insert(L, -2); + lua_settable(L, -3); + + lua_pop(L, 1); + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return 1; +} + +static int startFrame(lua_State *L) { + GraphicEngine *pGE = getGE(); + + if (lua_gettop(L) == 0) + lua_pushbooleancpp(L, pGE->startFrame()); + else + lua_pushbooleancpp(L, pGE->startFrame(lua_tobooleancpp(L, 1))); + + return 1; +} + +static int endFrame(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushbooleancpp(L, pGE->endFrame()); + + return 1; +} + +static int drawDebugLine(lua_State *L) { + GraphicEngine *pGE = getGE(); + + Vertex start; + Vertex end; + Vertex::luaVertexToVertex(L, 1, start); + Vertex::luaVertexToVertex(L, 2, end); + pGE->drawDebugLine(start, end, GraphicEngine::luaColorToARGBColor(L, 3)); + + return 0; +} + +static int getDisplayWidth(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getDisplayWidth()); + + return 1; +} + +static int getDisplayHeight(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getDisplayHeight()); + + return 1; +} + +static int getBitDepth(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getBitDepth()); + + return 1; +} + +static int setVsync(lua_State *L) { + GraphicEngine *pGE = getGE(); + + pGE->setVsync(lua_tobooleancpp(L, 1)); + + return 0; +} + +static int isVsync(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushbooleancpp(L, pGE->getVsync()); + + return 1; +} + +static int isWindowed(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushbooleancpp(L, pGE->isWindowed()); + + return 1; +} + +static int getFPSCount(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getFPSCount()); + + return 1; +} + +static int getLastFrameDuration(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getLastFrameDuration()); + + return 1; +} + +static int stopMainTimer(lua_State *L) { + GraphicEngine *pGE = getGE(); + pGE->stopMainTimer(); + return 0; +} + +static int resumeMainTimer(lua_State *L) { + GraphicEngine *pGE = getGE(); + pGE->resumeMainTimer(); + return 0; +} + +static int getSecondaryFrameDuration(lua_State *L) { + GraphicEngine *pGE = getGE(); + + lua_pushnumber(L, pGE->getSecondaryFrameDuration()); + + return 1; +} + +static int saveScreenshot(lua_State *L) { + // This is used by system/debug.lua only. We do not implement this; support + // for taking screenshots is a backend feature. + lua_pushbooleancpp(L, false); + + return 1; +} + +static int saveThumbnailScreenshot(lua_State *L) { + GraphicEngine *pGE = getGE(); + lua_pushbooleancpp(L, pGE->saveThumbnailScreenshot(luaL_checkstring(L, 1))); + return 1; +} + +static int getRepaintedPixels(lua_State *L) { + GraphicEngine *pGE = getGE(); + lua_pushnumber(L, static_cast<lua_Number>(pGE->getRepaintedPixels())); + return 1; +} + +static const luaL_reg GFX_FUNCTIONS[] = { + {"Init", init}, + {"StartFrame", startFrame}, + {"EndFrame", endFrame}, + {"DrawDebugLine", drawDebugLine}, + {"SetVsync", setVsync}, + {"GetDisplayWidth", getDisplayWidth}, + {"GetDisplayHeight", getDisplayHeight}, + {"GetBitDepth", getBitDepth}, + {"IsVsync", isVsync}, + {"IsWindowed", isWindowed}, + {"GetFPSCount", getFPSCount}, + {"GetLastFrameDuration", getLastFrameDuration}, + {"StopMainTimer", stopMainTimer}, + {"ResumeMainTimer", resumeMainTimer}, + {"GetSecondaryFrameDuration", getSecondaryFrameDuration}, + {"SaveScreenshot", saveScreenshot}, + {"NewAnimationTemplate", newAnimationTemplate}, + {"GetRepaintedPixels", getRepaintedPixels}, + {"SaveThumbnailScreenshot", saveThumbnailScreenshot}, + {0, 0} +}; + +static RenderObjectPtr<RenderObject> checkRenderObject(lua_State *L, bool errorIfRemoved = true) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable einer Klasse haben, die von Gfx.RenderObject "erbt". + uint *userDataPtr; + if ((userDataPtr = (uint *) my_checkudata(L, 1, BITMAP_CLASS_NAME)) != 0 || + (userDataPtr = (uint *) my_checkudata(L, 1, ANIMATION_CLASS_NAME)) != 0 || + (userDataPtr = (uint *) my_checkudata(L, 1, PANEL_CLASS_NAME)) != 0 || + (userDataPtr = (uint *) my_checkudata(L, 1, TEXT_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) + return roPtr; + else { + if (errorIfRemoved) + luaL_error(L, "The renderobject with the handle %d does no longer exist.", *userDataPtr); + } + } else { + luaL_argcheck(L, 0, 1, "'" RENDEROBJECT_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<RenderObject>(); +} + +static int ro_setPos(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + Vertex pos; + Vertex::luaVertexToVertex(L, 2, pos); + roPtr->setPos(pos.x, pos.y); + return 0; +} + +static int ro_setX(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr->setX(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int ro_setY(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr->setY(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int ro_setZ(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr->setZ(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int ro_setVisible(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr->setVisible(lua_tobooleancpp(L, 2)); + return 0; +} + +static int ro_getX(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getX()); + + return 1; +} + +static int ro_getY(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getY()); + + return 1; +} + +static int ro_getZ(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getZ()); + + return 1; +} + +static int ro_getAbsoluteX(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getAbsoluteX()); + + return 1; +} + +static int ro_getAbsoluteY(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getAbsoluteY()); + + return 1; +} + +static int ro_getWidth(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getWidth()); + + return 1; +} + +static int ro_getHeight(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushnumber(L, roPtr->getHeight()); + + return 1; +} + +static int ro_isVisible(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + lua_pushbooleancpp(L, roPtr->isVisible()); + + return 1; +} + +static int ro_addPanel(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + RenderObjectPtr<Panel> panelPtr = roPtr->addPanel(static_cast<int>(luaL_checknumber(L, 2)), + static_cast<int>(luaL_checknumber(L, 3)), + GraphicEngine::luaColorToARGBColor(L, 4)); + if (panelPtr.isValid()) { + newUintUserData(L, panelPtr->getHandle()); + // luaL_getmetatable(L, PANEL_CLASS_NAME); + LuaBindhelper::getMetatable(L, PANEL_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + } else + lua_pushnil(L); + + return 1; +} + +static int ro_addBitmap(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + RenderObjectPtr<Bitmap> bitmaPtr = roPtr->addBitmap(luaL_checkstring(L, 2)); + if (bitmaPtr.isValid()) { + newUintUserData(L, bitmaPtr->getHandle()); + // luaL_getmetatable(L, BITMAP_CLASS_NAME); + LuaBindhelper::getMetatable(L, BITMAP_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + } else + lua_pushnil(L); + + return 1; +} + +static int ro_addText(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + + RenderObjectPtr<Text> textPtr; + if (lua_gettop(L) >= 3) + textPtr = roPtr->addText(luaL_checkstring(L, 2), luaL_checkstring(L, 3)); + else + textPtr = roPtr->addText(luaL_checkstring(L, 2)); + + if (textPtr.isValid()) { + newUintUserData(L, textPtr->getHandle()); + // luaL_getmetatable(L, TEXT_CLASS_NAME); + LuaBindhelper::getMetatable(L, TEXT_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + } else + lua_pushnil(L); + + return 1; +} + +static int ro_addAnimation(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + + RenderObjectPtr<Animation> animationPtr; + if (lua_type(L, 2) == LUA_TUSERDATA) + animationPtr = roPtr->addAnimation(*checkAnimationTemplate(L, 2)); + else + animationPtr = roPtr->addAnimation(luaL_checkstring(L, 2)); + + if (animationPtr.isValid()) { + newUintUserData(L, animationPtr->getHandle()); + // luaL_getmetatable(L, ANIMATION_CLASS_NAME); + LuaBindhelper::getMetatable(L, ANIMATION_CLASS_NAME); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); + + // Alle Animationscallbacks registrieren. + animationPtr->setCallbacks(); + } else + lua_pushnil(L); + + return 1; +} + +void Animation::setCallbacks() { + _actionCallback = animationActionCallback; + _loopPointCallback = animationLoopPointCallback; + _deleteCallback = animationDeleteCallback; +} + +static const luaL_reg RENDEROBJECT_METHODS[] = { + {"AddAnimation", ro_addAnimation}, + {"AddText", ro_addText}, + {"AddBitmap", ro_addBitmap}, + {"AddPanel", ro_addPanel}, + {"SetPos", ro_setPos}, + {"SetX", ro_setX}, + {"SetY", ro_setY}, + {"SetZ", ro_setZ}, + {"SetVisible", ro_setVisible}, + {"GetX", ro_getX}, + {"GetY", ro_getY}, + {"GetZ", ro_getZ}, + {"GetAbsoluteX", ro_getAbsoluteX}, + {"GetAbsoluteY", ro_getAbsoluteY}, + {"GetWidth", ro_getWidth}, + {"GetHeight", ro_getHeight}, + {"IsVisible", ro_isVisible}, + {0, 0} +}; + +static RenderObjectPtr<Panel> checkPanel(lua_State *L) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.Panel + uint *userDataPtr; + if ((userDataPtr = (uint *)my_checkudata(L, 1, PANEL_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) { + return roPtr->toPanel(); + } else + luaL_error(L, "The panel with the handle %d does no longer exist.", *userDataPtr); + } else { + luaL_argcheck(L, 0, 1, "'" PANEL_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<Panel>(); +} + +static int p_getColor(lua_State *L) { + RenderObjectPtr<Panel> PanelPtr = checkPanel(L); + BS_ASSERT(PanelPtr.isValid()); + GraphicEngine::ARGBColorToLuaColor(L, PanelPtr->getColor()); + + return 1; +} + +static int p_setColor(lua_State *L) { + RenderObjectPtr<Panel> PanelPtr = checkPanel(L); + BS_ASSERT(PanelPtr.isValid()); + PanelPtr->setColor(GraphicEngine::luaColorToARGBColor(L, 2)); + return 0; +} + +static int p_remove(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr.erase(); + return 0; +} + +static const luaL_reg PANEL_METHODS[] = { + {"GetColor", p_getColor}, + {"SetColor", p_setColor}, + {"Remove", p_remove}, + {0, 0} +}; + +static RenderObjectPtr<Bitmap> checkBitmap(lua_State *L) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.Bitmap + uint *userDataPtr; + if ((userDataPtr = (uint *)my_checkudata(L, 1, BITMAP_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) { + return roPtr->toBitmap(); + } else + luaL_error(L, "The bitmap with the handle %d does no longer exist.", *userDataPtr); + } else { + luaL_argcheck(L, 0, 1, "'" BITMAP_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<Bitmap>(); +} + +static int b_setAlpha(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setAlpha(static_cast<uint>(luaL_checknumber(L, 2))); + return 0; +} + +static int b_setTintColor(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setModulationColor(GraphicEngine::luaColorToARGBColor(L, 2)); + return 0; +} + +static int b_setScaleFactor(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setScaleFactor(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int b_setScaleFactorX(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setScaleFactorX(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int b_setScaleFactorY(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setScaleFactorY(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int b_setFlipH(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setFlipH(lua_tobooleancpp(L, 2)); + return 0; +} + +static int b_setFlipV(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + bitmapPtr->setFlipV(lua_tobooleancpp(L, 2)); + return 0; +} + +static int b_getAlpha(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushnumber(L, bitmapPtr->getAlpha()); + return 1; +} + +static int b_getTintColor(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + GraphicEngine::ARGBColorToLuaColor(L, bitmapPtr->getModulationColor()); + return 1; +} + +static int b_getScaleFactorX(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushnumber(L, bitmapPtr->getScaleFactorX()); + return 1; +} + +static int b_getScaleFactorY(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushnumber(L, bitmapPtr->getScaleFactorY()); + return 1; +} + +static int b_isFlipH(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isFlipH()); + return 1; +} + +static int b_isFlipV(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isFlipV()); + return 1; +} + +static int b_getPixel(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + Vertex Pos; + Vertex::luaVertexToVertex(L, 2, Pos); + GraphicEngine::ARGBColorToLuaColor(L, bitmapPtr->getPixel(Pos.x, Pos.y)); + return 1; +} + +static int b_isScalingAllowed(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isScalingAllowed()); + return 1; +} + +static int b_isAlphaAllowed(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isAlphaAllowed()); + return 1; +} + +static int b_isTintingAllowed(lua_State *L) { + RenderObjectPtr<Bitmap> bitmapPtr = checkBitmap(L); + BS_ASSERT(bitmapPtr.isValid()); + lua_pushbooleancpp(L, bitmapPtr->isColorModulationAllowed()); + return 1; +} + +static int b_remove(lua_State *L) { + RenderObjectPtr<RenderObject> roPtr = checkRenderObject(L); + BS_ASSERT(roPtr.isValid()); + roPtr.erase(); + return 0; +} + +static const luaL_reg BITMAP_METHODS[] = { + {"SetAlpha", b_setAlpha}, + {"SetTintColor", b_setTintColor}, + {"SetScaleFactor", b_setScaleFactor}, + {"SetScaleFactorX", b_setScaleFactorX}, + {"SetScaleFactorY", b_setScaleFactorY}, + {"SetFlipH", b_setFlipH}, + {"SetFlipV", b_setFlipV}, + {"GetAlpha", b_getAlpha}, + {"GetTintColor", b_getTintColor}, + {"GetScaleFactorX", b_getScaleFactorX}, + {"GetScaleFactorY", b_getScaleFactorY}, + {"IsFlipH", b_isFlipH}, + {"IsFlipV", b_isFlipV}, + {"GetPixel", b_getPixel}, + {"IsScalingAllowed", b_isScalingAllowed}, + {"IsAlphaAllowed", b_isAlphaAllowed}, + {"IsTintingAllowed", b_isTintingAllowed}, + {"Remove", b_remove}, + {0, 0} +}; + +static RenderObjectPtr<Animation> checkAnimation(lua_State *L) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.Animation + uint *userDataPtr; + if ((userDataPtr = (uint *)my_checkudata(L, 1, ANIMATION_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) + return roPtr->toAnimation(); + else { + luaL_error(L, "The animation with the handle %d does no longer exist.", *userDataPtr); + } + } else { + luaL_argcheck(L, 0, 1, "'" ANIMATION_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<Animation>(); +} + +static int a_play(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->play(); + return 0; +} + +static int a_pause(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->pause(); + return 0; +} + +static int a_stop(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->stop(); + return 0; +} + +static int a_setFrame(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setFrame(static_cast<uint>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_setAlpha(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setAlpha(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_setTintColor(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setModulationColor(GraphicEngine::luaColorToARGBColor(L, 2)); + return 0; +} + +static int a_setScaleFactor(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setScaleFactor(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_setScaleFactorX(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setScaleFactorX(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_setScaleFactorY(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr->setScaleFactorY(static_cast<float>(luaL_checknumber(L, 2))); + return 0; +} + +static int a_getScaleFactorX(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getScaleFactorX()); + return 1; +} + +static int a_getScaleFactorY(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getScaleFactorY()); + return 1; +} + +static int a_getAnimationType(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + switch (animationPtr->getAnimationType()) { + case Animation::AT_JOJO: + lua_pushstring(L, "jojo"); + break; + case Animation::AT_LOOP: + lua_pushstring(L, "loop"); + break; + case Animation::AT_ONESHOT: + lua_pushstring(L, "oneshot"); + break; + default: + BS_ASSERT(false); + } + return 1; +} + +static int a_getFPS(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getFPS()); + return 1; +} + +static int a_getFrameCount(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getFrameCount()); + return 1; +} + +static int a_isScalingAllowed(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushbooleancpp(L, animationPtr->isScalingAllowed()); + return 1; +} + +static int a_isAlphaAllowed(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushbooleancpp(L, animationPtr->isAlphaAllowed()); + return 1; +} + +static int a_isTintingAllowed(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushbooleancpp(L, animationPtr->isColorModulationAllowed()); + return 1; +} + +static int a_getCurrentFrame(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushnumber(L, animationPtr->getCurrentFrame()); + return 1; +} + +static int a_getCurrentAction(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushstring(L, animationPtr->getCurrentAction().c_str()); + return 1; +} + +static int a_isPlaying(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + lua_pushbooleancpp(L, animationPtr->isRunning()); + return 1; +} + +static bool animationLoopPointCallback(uint handle) { + lua_State *L = static_cast<lua_State *>(Kernel::getInstance()->getScript()->getScriptObject()); + loopPointCallbackPtr->invokeCallbackFunctions(L, handle); + + return true; +} + +static int a_registerLoopPointCallback(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + luaL_checktype(L, 2, LUA_TFUNCTION); + + lua_pushvalue(L, 2); + loopPointCallbackPtr->registerCallbackFunction(L, animationPtr->getHandle()); + + return 0; +} + +static int a_unregisterLoopPointCallback(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + luaL_checktype(L, 2, LUA_TFUNCTION); + + lua_pushvalue(L, 2); + loopPointCallbackPtr->unregisterCallbackFunction(L, animationPtr->getHandle()); + + return 0; +} + +static bool animationActionCallback(uint Handle) { + RenderObjectPtr<Animation> animationPtr(Handle); + if (animationPtr.isValid()) { + actionCallbackPtr->Action = animationPtr->getCurrentAction(); + lua_State *L = static_cast<lua_State *>(Kernel::getInstance()->getScript()->getScriptObject()); + actionCallbackPtr->invokeCallbackFunctions(L, animationPtr->getHandle()); + } + + return true; +} + +static int a_registerActionCallback(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + luaL_checktype(L, 2, LUA_TFUNCTION); + + lua_pushvalue(L, 2); + actionCallbackPtr->registerCallbackFunction(L, animationPtr->getHandle()); + + return 0; +} + +static int a_unregisterActionCallback(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + luaL_checktype(L, 2, LUA_TFUNCTION); + + lua_pushvalue(L, 2); + actionCallbackPtr->unregisterCallbackFunction(L, animationPtr->getHandle()); + + return 0; +} + +static bool animationDeleteCallback(uint Handle) { + lua_State *L = static_cast<lua_State *>(Kernel::getInstance()->getScript()->getScriptObject()); + loopPointCallbackPtr->removeAllObjectCallbacks(L, Handle); + + return true; +} + +static int a_remove(lua_State *L) { + RenderObjectPtr<Animation> animationPtr = checkAnimation(L); + BS_ASSERT(animationPtr.isValid()); + animationPtr.erase(); + return 0; +} + +static const luaL_reg ANIMATION_METHODS[] = { + {"Play", a_play}, + {"Pause", a_pause}, + {"Stop", a_stop}, + {"SetFrame", a_setFrame}, + {"SetAlpha", a_setAlpha}, + {"SetTintColor", a_setTintColor}, + {"SetScaleFactor", a_setScaleFactor}, + {"SetScaleFactorX", a_setScaleFactorX}, + {"SetScaleFactorY", a_setScaleFactorY}, + {"GetScaleFactorX", a_getScaleFactorX}, + {"GetScaleFactorY", a_getScaleFactorY}, + {"GetAnimationType", a_getAnimationType}, + {"GetFPS", a_getFPS}, + {"GetFrameCount", a_getFrameCount}, + {"IsScalingAllowed", a_isScalingAllowed}, + {"IsAlphaAllowed", a_isAlphaAllowed}, + {"IsTintingAllowed", a_isTintingAllowed}, + {"GetCurrentFrame", a_getCurrentFrame}, + {"GetCurrentAction", a_getCurrentAction}, + {"IsPlaying", a_isPlaying}, + {"RegisterLoopPointCallback", a_registerLoopPointCallback}, + {"UnregisterLoopPointCallback", a_unregisterLoopPointCallback}, + {"RegisterActionCallback", a_registerActionCallback}, + {"UnregisterActionCallback", a_unregisterActionCallback}, + {"Remove", a_remove}, + {0, 0} +}; + +static RenderObjectPtr<Text> checkText(lua_State *L) { + // Der erste Parameter muss vom Typ userdata sein und die Metatable der Klasse Gfx.Text + uint *userDataPtr; + if ((userDataPtr = (uint *)my_checkudata(L, 1, TEXT_CLASS_NAME)) != 0) { + RenderObjectPtr<RenderObject> roPtr(*userDataPtr); + if (roPtr.isValid()) + return roPtr->toText(); + else + luaL_error(L, "The text with the handle %d does no longer exist.", *userDataPtr); + } else { + luaL_argcheck(L, 0, 1, "'" TEXT_CLASS_NAME "' expected"); + } + + return RenderObjectPtr<Text>(); +} + +static int t_setFont(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setFont(luaL_checkstring(L, 2)); + return 0; +} + +static int t_setText(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setText(luaL_checkstring(L, 2)); + return 0; +} + +static int t_setAlpha(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setAlpha(static_cast<int>(luaL_checknumber(L, 2))); + return 0; +} + +static int t_setColor(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setColor(GraphicEngine::luaColorToARGBColor(L, 2)); + return 0; +} + +static int t_setAutoWrap(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setAutoWrap(lua_tobooleancpp(L, 2)); + return 0; +} + +static int t_setAutoWrapThreshold(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr->setAutoWrapThreshold(static_cast<uint>(luaL_checknumber(L, 2))); + return 0; +} + +static int t_getText(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushstring(L, textPtr->getText().c_str()); + return 1; +} + +static int t_getFont(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushstring(L, textPtr->getFont().c_str()); + return 1; +} + +static int t_getAlpha(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushnumber(L, textPtr->getAlpha()); + return 1; +} + +static int t_getColor(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushnumber(L, textPtr->getColor()); + return 1; +} + +static int t_isAutoWrap(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushbooleancpp(L, textPtr->isAutoWrapActive()); + return 1; +} + +static int t_getAutoWrapThreshold(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + lua_pushnumber(L, textPtr->getAutoWrapThreshold()); + return 1; +} + +static int t_remove(lua_State *L) { + RenderObjectPtr<Text> textPtr = checkText(L); + BS_ASSERT(textPtr.isValid()); + textPtr.erase(); + return 0; +} + +static const luaL_reg TEXT_METHODS[] = { + {"SetFont", t_setFont}, + {"SetText", t_setText}, + {"SetAlpha", t_setAlpha}, + {"SetColor", t_setColor}, + {"SetAutoWrap", t_setAutoWrap}, + {"SetAutoWrapThreshold", t_setAutoWrapThreshold}, + {"GetText", t_getText}, + {"GetFont", t_getFont}, + {"GetAlpha", t_getAlpha}, + {"GetColor", t_getColor}, + {"IsAutoWrap", t_isAutoWrap}, + {"GetAutoWrapThreshold", t_getAutoWrapThreshold}, + {"Remove", t_remove}, + {0, 0} +}; + +bool GraphicEngine::registerScriptBindings() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ScriptEngine *pScript = pKernel->getScript(); + BS_ASSERT(pScript); + lua_State *L = static_cast<lua_State *>(pScript->getScriptObject()); + BS_ASSERT(L); + + if (!LuaBindhelper::addMethodsToClass(L, BITMAP_CLASS_NAME, RENDEROBJECT_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, ANIMATION_CLASS_NAME, RENDEROBJECT_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, PANEL_CLASS_NAME, RENDEROBJECT_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, TEXT_CLASS_NAME, RENDEROBJECT_METHODS)) return false; + + if (!LuaBindhelper::addMethodsToClass(L, PANEL_CLASS_NAME, PANEL_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, BITMAP_CLASS_NAME, BITMAP_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, TEXT_CLASS_NAME, TEXT_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, ANIMATION_CLASS_NAME, ANIMATION_METHODS)) return false; + + if (!LuaBindhelper::addMethodsToClass(L, ANIMATION_TEMPLATE_CLASS_NAME, ANIMATION_TEMPLATE_METHODS)) return false; + + if (!LuaBindhelper::addFunctionsToLib(L, GFX_LIBRARY_NAME, GFX_FUNCTIONS)) return false; + + assert(loopPointCallbackPtr == 0); + loopPointCallbackPtr = new LuaCallback(L); + + assert(actionCallbackPtr == 0); + actionCallbackPtr = new ActionCallback(L); + + return true; +} + +void GraphicEngine::unregisterScriptBindings() { + delete loopPointCallbackPtr; + loopPointCallbackPtr = 0; + + delete actionCallbackPtr; + actionCallbackPtr = 0; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/art.cpp b/engines/sword25/gfx/image/art.cpp new file mode 100644 index 0000000000..064ca333e7 --- /dev/null +++ b/engines/sword25/gfx/image/art.cpp @@ -0,0 +1,2653 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Libart_LGPL - library of basic graphic primitives + * + * Copyright (c) 1998 Raph Levien + * + * Licensed under GNU LGPL v2 + * + */ + +/* Various utility functions RLL finds useful. */ + +#include "sword25/gfx/image/art.h" + +namespace Sword25 { + +/** + * art_die: Print the error message to stderr and exit with a return code of 1. + * @fmt: The printf-style format for the error message. + * + * Used for dealing with severe errors. + **/ +void art_die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + exit(1); +} + +/** + * art_warn: Print the warning message to stderr. + * @fmt: The printf-style format for the warning message. + * + * Used for generating warnings. + **/ +void art_warn(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +/** + * art_svp_free: Free an #ArtSVP structure. + * @svp: #ArtSVP to free. + * + * Frees an #ArtSVP structure and all the segments in it. + **/ +void art_svp_free(ArtSVP *svp) { + int n_segs = svp->n_segs; + int i; + + for (i = 0; i < n_segs; i++) + free(svp->segs[i].points); + free(svp); +} + +#define EPSILON 0 + +/** + * art_svp_seg_compare: Compare two segments of an svp. + * @seg1: First segment to compare. + * @seg2: Second segment to compare. + * + * Compares two segments of an svp. Return 1 if @seg2 is below or to the + * right of @seg1, -1 otherwise. + **/ +int art_svp_seg_compare(const void *s1, const void *s2) { + const ArtSVPSeg *seg1 = (const ArtSVPSeg *)s1; + const ArtSVPSeg *seg2 = (const ArtSVPSeg *)s2; + + if (seg1->points[0].y - EPSILON > seg2->points[0].y) return 1; + else if (seg1->points[0].y + EPSILON < seg2->points[0].y) return -1; + else if (seg1->points[0].x - EPSILON > seg2->points[0].x) return 1; + else if (seg1->points[0].x + EPSILON < seg2->points[0].x) return -1; + else if ((seg1->points[1].x - seg1->points[0].x) * + (seg2->points[1].y - seg2->points[0].y) - + (seg1->points[1].y - seg1->points[0].y) * + (seg2->points[1].x - seg2->points[0].x) > 0) return 1; + else return -1; +} + +/** + * art_vpath_add_point: Add point to vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @code: The pathcode for the new point. + * @x: The X coordinate of the new point. + * @y: The Y coordinate of the new point. + * + * Adds a new point to *@p_vpath, reallocating and updating *@p_vpath + * and *@pn_points_max as necessary. *@pn_points is incremented. + * + * This routine always adds the point after all points already in the + * vpath. Thus, it should be called in the order the points are + * desired. + **/ +void art_vpath_add_point(ArtVpath **p_vpath, int *pn_points, int *pn_points_max, + ArtPathcode code, double x, double y) { + int i; + + i = (*pn_points)++; + if (i == *pn_points_max) + art_expand(*p_vpath, ArtVpath, *pn_points_max); + (*p_vpath)[i].code = code; + (*p_vpath)[i].x = x; + (*p_vpath)[i].y = y; +} + +/* Sort vector paths into sorted vector paths */ + +/* reverse a list of points in place */ +static void reverse_points(ArtPoint *points, int n_points) { + int i; + ArtPoint tmp_p; + + for (i = 0; i < (n_points >> 1); i++) { + tmp_p = points[i]; + points[i] = points[n_points - (i + 1)]; + points[n_points - (i + 1)] = tmp_p; + } +} + +/** + * art_svp_from_vpath: Convert a vpath to a sorted vector path. + * @vpath: #ArtVPath to convert. + * + * Converts a vector path into sorted vector path form. The svp form is + * more efficient for rendering and other vector operations. + * + * Basically, the implementation is to traverse the vector path, + * generating a new segment for each "run" of points in the vector + * path with monotonically increasing Y values. All the resulting + * values are then sorted. + * + * Note: I'm not sure that the sorting rule is correct with respect + * to numerical stability issues. + * + * Return value: Resulting sorted vector path. + **/ +ArtSVP *art_svp_from_vpath(ArtVpath *vpath) { + int n_segs, n_segs_max; + ArtSVP *svp; + int dir; + int new_dir; + int i; + ArtPoint *points; + int n_points, n_points_max; + double x, y; + double x_min, x_max; + + n_segs = 0; + n_segs_max = 16; + svp = (ArtSVP *)malloc(sizeof(ArtSVP) + + (n_segs_max - 1) * sizeof(ArtSVPSeg)); + + dir = 0; + n_points = 0; + n_points_max = 0; + points = NULL; + i = 0; + + x = y = 0; /* unnecessary, given "first code must not be LINETO" invariant, + but it makes gcc -Wall -ansi -pedantic happier */ + x_min = x_max = 0; /* same */ + + while (vpath[i].code != ART_END) { + if (vpath[i].code == ART_MOVETO || vpath[i].code == ART_MOVETO_OPEN) { + if (points != NULL && n_points >= 2) { + if (n_segs == n_segs_max) { + n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points(points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + points = NULL; + } + + if (points == NULL) { + n_points_max = 4; + points = art_new(ArtPoint, n_points_max); + } + + n_points = 1; + points[0].x = x = vpath[i].x; + points[0].y = y = vpath[i].y; + x_min = x; + x_max = x; + dir = 0; + } else { /* must be LINETO */ + new_dir = (vpath[i].y > y || + (vpath[i].y == y && vpath[i].x > x)) ? 1 : -1; + if (dir && dir != new_dir) { + /* new segment */ + x = points[n_points - 1].x; + y = points[n_points - 1].y; + if (n_segs == n_segs_max) { + n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points(points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + + n_points = 1; + n_points_max = 4; + points = art_new(ArtPoint, n_points_max); + points[0].x = x; + points[0].y = y; + x_min = x; + x_max = x; + } + + if (points != NULL) { + if (n_points == n_points_max) + art_expand(points, ArtPoint, n_points_max); + points[n_points].x = x = vpath[i].x; + points[n_points].y = y = vpath[i].y; + if (x < x_min) x_min = x; + else if (x > x_max) x_max = x; + n_points++; + } + dir = new_dir; + } + i++; + } + + if (points != NULL) { + if (n_points >= 2) { + if (n_segs == n_segs_max) { + n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points(points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + } else + free(points); + } + + svp->n_segs = n_segs; + + qsort(&svp->segs, n_segs, sizeof(ArtSVPSeg), art_svp_seg_compare); + + return svp; +} + + +/* Basic constructors and operations for bezier paths */ + +#define RENDER_LEVEL 4 +#define RENDER_SIZE (1 << (RENDER_LEVEL)) + +/** + * art_vpath_render_bez: Render a bezier segment into the vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @x0: X coordinate of starting bezier point. + * @y0: Y coordinate of starting bezier point. + * @x1: X coordinate of first bezier control point. + * @y1: Y coordinate of first bezier control point. + * @x2: X coordinate of second bezier control point. + * @y2: Y coordinate of second bezier control point. + * @x3: X coordinate of ending bezier point. + * @y3: Y coordinate of ending bezier point. + * @flatness: Flatness control. + * + * Renders a bezier segment into the vector path, reallocating and + * updating *@p_vpath and *@pn_vpath_max as necessary. *@pn_vpath is + * incremented by the number of vector points added. + * + * This step includes (@x0, @y0) but not (@x3, @y3). + * + * The @flatness argument guides the amount of subdivision. The Adobe + * PostScript reference manual defines flatness as the maximum + * deviation between the any point on the vpath approximation and the + * corresponding point on the "true" curve, and we follow this + * definition here. A value of 0.25 should ensure high quality for aa + * rendering. +**/ +static void art_vpath_render_bez(ArtVpath **p_vpath, int *pn, int *pn_max, + double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3, + double flatness) { + double x3_0, y3_0; + double z3_0_dot; + double z1_dot, z2_dot; + double z1_perp, z2_perp; + double max_perp_sq; + + double x_m, y_m; + double xa1, ya1; + double xa2, ya2; + double xb1, yb1; + double xb2, yb2; + + /* It's possible to optimize this routine a fair amount. + + First, once the _dot conditions are met, they will also be met in + all further subdivisions. So we might recurse to a different + routine that only checks the _perp conditions. + + Second, the distance _should_ decrease according to fairly + predictable rules (a factor of 4 with each subdivision). So it might + be possible to note that the distance is within a factor of 4 of + acceptable, and subdivide once. But proving this might be hard. + + Third, at the last subdivision, x_m and y_m can be computed more + expeditiously (as in the routine above). + + Finally, if we were able to subdivide by, say 2 or 3, this would + allow considerably finer-grain control, i.e. fewer points for the + same flatness tolerance. This would speed things up downstream. + + In any case, this routine is unlikely to be the bottleneck. It's + just that I have this undying quest for more speed... + + */ + + x3_0 = x3 - x0; + y3_0 = y3 - y0; + + /* z3_0_dot is dist z0-z3 squared */ + z3_0_dot = x3_0 * x3_0 + y3_0 * y3_0; + + if (z3_0_dot < 0.001) { + /* if start and end point are almost identical, the flatness tests + * don't work properly, so fall back on testing whether both of + * the other two control points are the same as the start point, + * too. + */ + if (hypot(x1 - x0, y1 - y0) < 0.001 + && hypot(x2 - x0, y2 - y0) < 0.001) + goto nosubdivide; + else + goto subdivide; + } + + /* we can avoid subdivision if: + + z1 has distance no more than flatness from the z0-z3 line + + z1 is no more z0'ward than flatness past z0-z3 + + z1 is more z0'ward than z3'ward on the line traversing z0-z3 + + and correspondingly for z2 */ + + /* perp is distance from line, multiplied by dist z0-z3 */ + max_perp_sq = flatness * flatness * z3_0_dot; + + z1_perp = (y1 - y0) * x3_0 - (x1 - x0) * y3_0; + if (z1_perp * z1_perp > max_perp_sq) + goto subdivide; + + z2_perp = (y3 - y2) * x3_0 - (x3 - x2) * y3_0; + if (z2_perp * z2_perp > max_perp_sq) + goto subdivide; + + z1_dot = (x1 - x0) * x3_0 + (y1 - y0) * y3_0; + if (z1_dot < 0 && z1_dot * z1_dot > max_perp_sq) + goto subdivide; + + z2_dot = (x3 - x2) * x3_0 + (y3 - y2) * y3_0; + if (z2_dot < 0 && z2_dot * z2_dot > max_perp_sq) + goto subdivide; + + if (z1_dot + z1_dot > z3_0_dot) + goto subdivide; + + if (z2_dot + z2_dot > z3_0_dot) + goto subdivide; + + +nosubdivide: + /* don't subdivide */ + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, x3, y3); + return; + +subdivide: + + xa1 = (x0 + x1) * 0.5; + ya1 = (y0 + y1) * 0.5; + xa2 = (x0 + 2 * x1 + x2) * 0.25; + ya2 = (y0 + 2 * y1 + y2) * 0.25; + xb1 = (x1 + 2 * x2 + x3) * 0.25; + yb1 = (y1 + 2 * y2 + y3) * 0.25; + xb2 = (x2 + x3) * 0.5; + yb2 = (y2 + y3) * 0.5; + x_m = (xa2 + xb1) * 0.5; + y_m = (ya2 + yb1) * 0.5; + + art_vpath_render_bez(p_vpath, pn, pn_max, + x0, y0, xa1, ya1, xa2, ya2, x_m, y_m, flatness); + art_vpath_render_bez(p_vpath, pn, pn_max, + x_m, y_m, xb1, yb1, xb2, yb2, x3, y3, flatness); +} + +/** + * art_bez_path_to_vec: Create vpath from bezier path. + * @bez: Bezier path. + * @flatness: Flatness control. + * + * Creates a vector path closely approximating the bezier path defined by + * @bez. The @flatness argument controls the amount of subdivision. In + * general, the resulting vpath deviates by at most @flatness pixels + * from the "ideal" path described by @bez. + * + * Return value: Newly allocated vpath. + **/ +ArtVpath *art_bez_path_to_vec(const ArtBpath *bez, double flatness) { + ArtVpath *vec; + int vec_n, vec_n_max; + int bez_index; + double x, y; + + vec_n = 0; + vec_n_max = RENDER_SIZE; + vec = art_new(ArtVpath, vec_n_max); + + /* Initialization is unnecessary because of the precondition that the + bezier path does not begin with LINETO or CURVETO, but is here + to make the code warning-free. */ + x = 0; + y = 0; + + bez_index = 0; + do { + /* make sure space for at least one more code */ + if (vec_n >= vec_n_max) + art_expand(vec, ArtVpath, vec_n_max); + switch (bez[bez_index].code) { + case ART_MOVETO_OPEN: + case ART_MOVETO: + case ART_LINETO: + x = bez[bez_index].x3; + y = bez[bez_index].y3; + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = x; + vec[vec_n].y = y; + vec_n++; + break; + case ART_END: + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = 0; + vec[vec_n].y = 0; + vec_n++; + break; + case ART_CURVETO: + art_vpath_render_bez(&vec, &vec_n, &vec_n_max, + x, y, + bez[bez_index].x1, bez[bez_index].y1, + bez[bez_index].x2, bez[bez_index].y2, + bez[bez_index].x3, bez[bez_index].y3, + flatness); + x = bez[bez_index].x3; + y = bez[bez_index].y3; + break; + } + } while (bez[bez_index++].code != ART_END); + return vec; +} + + +#define EPSILON_6 1e-6 +#define EPSILON_2 1e-12 + +/* Render an arc segment starting at (xc + x0, yc + y0) to (xc + x1, + yc + y1), centered at (xc, yc), and with given radius. Both x0^2 + + y0^2 and x1^2 + y1^2 should be equal to radius^2. + + A positive value of radius means curve to the left, negative means + curve to the right. +*/ +static void art_svp_vpath_stroke_arc(ArtVpath **p_vpath, int *pn, int *pn_max, + double xc, double yc, + double x0, double y0, + double x1, double y1, + double radius, + double flatness) { + double theta; + double th_0, th_1; + int n_pts; + int i; + double aradius; + + aradius = fabs(radius); + theta = 2 * M_SQRT2 * sqrt(flatness / aradius); + th_0 = atan2(y0, x0); + th_1 = atan2(y1, x1); + if (radius > 0) { + /* curve to the left */ + if (th_0 < th_1) th_0 += M_PI * 2; + n_pts = (int)ceil((th_0 - th_1) / theta); + } else { + /* curve to the right */ + if (th_1 < th_0) th_1 += M_PI * 2; + n_pts = (int)ceil((th_1 - th_0) / theta); + } + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, xc + x0, yc + y0); + for (i = 1; i < n_pts; i++) { + theta = th_0 + (th_1 - th_0) * i / n_pts; + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, xc + cos(theta) * aradius, + yc + sin(theta) * aradius); + } + art_vpath_add_point(p_vpath, pn, pn_max, + ART_LINETO, xc + x1, yc + y1); +} + +/* Assume that forw and rev are at point i0. Bring them to i1, + joining with the vector i1 - i2. + + This used to be true, but isn't now that the stroke_raw code is + filtering out (near)zero length vectors: {It so happens that all + invocations of this function maintain the precondition i1 = i0 + 1, + so we could decrease the number of arguments by one. We haven't + done that here, though.} + + forw is to the line's right and rev is to its left. + + Precondition: no zero-length vectors, otherwise a divide by + zero will happen. */ +static void render_seg(ArtVpath **p_forw, int *pn_forw, int *pn_forw_max, + ArtVpath **p_rev, int *pn_rev, int *pn_rev_max, + ArtVpath *vpath, int i0, int i1, int i2, + ArtPathStrokeJoinType join, + double line_width, double miter_limit, double flatness) { + double dx0, dy0; + double dx1, dy1; + double dlx0, dly0; + double dlx1, dly1; + double dmx, dmy; + double dmr2; + double scale; + double cross; + + /* The vectors of the lines from i0 to i1 and i1 to i2. */ + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + dx1 = vpath[i2].x - vpath[i1].x; + dy1 = vpath[i2].y - vpath[i1].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt(dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + + /* Set dl[xy]1 to the vector from i1 to i2, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt(dx1 * dx1 + dy1 * dy1); + dlx1 = dy1 * scale; + dly1 = -dx1 * scale; + + /* now, forw's last point is expected to be colinear along d[xy]0 + to point i0 - dl[xy]0, and rev with i0 + dl[xy]0. */ + + /* positive for positive area (i.e. left turn) */ + cross = dx1 * dy0 - dx0 * dy1; + + dmx = (dlx0 + dlx1) * 0.5; + dmy = (dly0 + dly1) * 0.5; + dmr2 = dmx * dmx + dmy * dmy; + + if (join == ART_PATH_STROKE_JOIN_MITER && + dmr2 * miter_limit * miter_limit < line_width * line_width) + join = ART_PATH_STROKE_JOIN_BEVEL; + + /* the case when dmr2 is zero or very small bothers me + (i.e. near a 180 degree angle) + ALEX: So, we avoid the optimization when dmr2 is very small. This should + be safe since dmx/y is only used in optimization and in MITER case, and MITER + should be converted to BEVEL when dmr2 is very small. */ + if (dmr2 > EPSILON_2) { + scale = line_width * line_width / dmr2; + dmx *= scale; + dmy *= scale; + } + + if (cross *cross < EPSILON_2 && dx0 *dx1 + dy0 *dy1 >= 0) { + /* going straight */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + } else if (cross > 0) { + /* left turn, forw is outside and rev is inside */ + + if ( + (dmr2 > EPSILON_2) && + /* check that i1 + dm[xy] is inside i0-i1 rectangle */ + (dx0 + dmx) * dx0 + (dy0 + dmy) * dy0 > 0 && + /* and that i1 + dm[xy] is inside i1-i2 rectangle */ + ((dx1 - dmx) * dx1 + (dy1 - dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 + dl[xy]1 is inside i0-i1 rectangle */ + (dx0 + dlx1) * dx0 + (dy0 + dly1) * dy0 > 0 && + /* and that i1 + dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 - dlx0) * dx1 + (dy1 - dly0) * dy1 > 0) +#endif + ) { + /* can safely add single intersection point */ + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } else { + /* need to loop-de-loop the inside */ + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) { + /* bevel */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } else if (join == ART_PATH_STROKE_JOIN_MITER) { + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } else if (join == ART_PATH_STROKE_JOIN_ROUND) + art_svp_vpath_stroke_arc(p_forw, pn_forw, pn_forw_max, + vpath[i1].x, vpath[i1].y, + -dlx0, -dly0, + -dlx1, -dly1, + line_width, + flatness); + } else { + /* right turn, rev is outside and forw is inside */ + + if ( + (dmr2 > EPSILON_2) && + /* check that i1 - dm[xy] is inside i0-i1 rectangle */ + (dx0 - dmx) * dx0 + (dy0 - dmy) * dy0 > 0 && + /* and that i1 - dm[xy] is inside i1-i2 rectangle */ + ((dx1 + dmx) * dx1 + (dy1 + dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 - dl[xy]1 is inside i0-i1 rectangle */ + (dx0 - dlx1) * dx0 + (dy0 - dly1) * dy0 > 0 && + /* and that i1 - dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 + dlx0) * dx1 + (dy1 + dly0) * dy1 > 0) +#endif + ) { + /* can safely add single intersection point */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } else { + /* need to loop-de-loop the inside */ + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point(p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) { + /* bevel */ + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } else if (join == ART_PATH_STROKE_JOIN_MITER) { + art_vpath_add_point(p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } else if (join == ART_PATH_STROKE_JOIN_ROUND) + art_svp_vpath_stroke_arc(p_rev, pn_rev, pn_rev_max, + vpath[i1].x, vpath[i1].y, + dlx0, dly0, + dlx1, dly1, + -line_width, + flatness); + + } +} + +/* caps i1, under the assumption of a vector from i0 */ +static void render_cap(ArtVpath **p_result, int *pn_result, int *pn_result_max, + ArtVpath *vpath, int i0, int i1, + ArtPathStrokeCapType cap, double line_width, double flatness) { + double dx0, dy0; + double dlx0, dly0; + double scale; + int n_pts; + int i; + + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt(dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + + switch (cap) { + case ART_PATH_STROKE_CAP_BUTT: + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_ROUND: + n_pts = (int)ceil(M_PI / (2.0 * M_SQRT2 * sqrt(flatness / line_width))); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + for (i = 1; i < n_pts; i++) { + double theta, c_th, s_th; + + theta = M_PI * i / n_pts; + c_th = cos(theta); + s_th = sin(theta); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 * c_th - dly0 * s_th, + vpath[i1].y - dly0 * c_th + dlx0 * s_th); + } + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_SQUARE: + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 - dly0, + vpath[i1].y - dly0 + dlx0); + art_vpath_add_point(p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x + dlx0 - dly0, + vpath[i1].y + dly0 + dlx0); + break; + } +} + +/** + * art_svp_from_vpath_raw: Stroke a vector path, raw version + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Exactly the same as art_svp_vpath_stroke(), except that the resulting + * stroke outline may self-intersect and have regions of winding number + * greater than 1. + * + * Return value: Resulting raw stroked outline in svp format. + **/ +ArtVpath *art_svp_vpath_stroke_raw(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) { + int begin_idx, end_idx; + int i; + ArtVpath *forw, *rev; + int n_forw, n_rev; + int n_forw_max, n_rev_max; + ArtVpath *result; + int n_result, n_result_max; + double half_lw = 0.5 * line_width; + int closed; + int last, this_, next, second; + double dx, dy; + + n_forw_max = 16; + forw = art_new(ArtVpath, n_forw_max); + + n_rev_max = 16; + rev = art_new(ArtVpath, n_rev_max); + + n_result = 0; + n_result_max = 16; + result = art_new(ArtVpath, n_result_max); + + for (begin_idx = 0; vpath[begin_idx].code != ART_END; begin_idx = end_idx) { + n_forw = 0; + n_rev = 0; + + closed = (vpath[begin_idx].code == ART_MOVETO); + + /* we don't know what the first point joins with until we get to the + last point and see if it's closed. So we start with the second + line in the path. + + Note: this is not strictly true (we now know it's closed from + the opening pathcode), but why fix code that isn't broken? + */ + + this_ = begin_idx; + /* skip over identical points at the beginning of the subpath */ + for (i = this_ + 1; vpath[i].code == ART_LINETO; i++) { + dx = vpath[i].x - vpath[this_].x; + dy = vpath[i].y - vpath[this_].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + second = next; + + /* invariant: this doesn't coincide with next */ + while (vpath[next].code == ART_LINETO) { + last = this_; + this_ = next; + /* skip over identical points after the beginning of the subpath */ + for (i = this_ + 1; vpath[i].code == ART_LINETO; i++) { + dx = vpath[i].x - vpath[this_].x; + dy = vpath[i].y - vpath[this_].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + if (vpath[next].code != ART_LINETO) { + /* reached end of path */ + /* make "closed" detection conform to PostScript + semantics (i.e. explicit closepath code rather than + just the fact that end of the path is the beginning) */ + if (closed && + vpath[this_].x == vpath[begin_idx].x && + vpath[this_].y == vpath[begin_idx].y) { + int j; + + /* path is closed, render join to beginning */ + render_seg(&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this_, second, + join, half_lw, miter_limit, flatness); + + /* do forward path */ + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_MOVETO, forw[n_forw - 1].x, + forw[n_forw - 1].y); + for (j = 0; j < n_forw; j++) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + + /* do reverse path, reversed */ + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_MOVETO, rev[0].x, + rev[0].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + } else { + /* path is open */ + int j; + + /* add to forw rather than result to ensure that + forw has at least one point. */ + render_cap(&forw, &n_forw, &n_forw_max, + vpath, last, this_, + cap, half_lw, flatness); + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_MOVETO, forw[0].x, + forw[0].y); + for (j = 1; j < n_forw; j++) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + render_cap(&result, &n_result, &n_result_max, + vpath, second, begin_idx, + cap, half_lw, flatness); + art_vpath_add_point(&result, &n_result, &n_result_max, + ART_LINETO, forw[0].x, + forw[0].y); + } + } else + render_seg(&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this_, next, + join, half_lw, miter_limit, flatness); + } + end_idx = next; + } + + free(forw); + free(rev); + art_vpath_add_point(&result, &n_result, &n_result_max, ART_END, 0, 0); + return result; +} + + +/* Render a vector path into a stroked outline. + + Status of this routine: + + Basic correctness: Only miter and bevel line joins are implemented, + and only butt line caps. Otherwise, seems to be fine. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: Should be pretty good. + + Precision: The perturbation fuzzes the coordinates slightly, + but not enough to be visible. */ + +/** + * art_svp_vpath_stroke: Stroke a vector path. + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Computes an svp representing the stroked outline of @vpath. The + * width of the stroked line is @line_width. + * + * Lines are joined according to the @join rule. Possible values are + * ART_PATH_STROKE_JOIN_MITER (for mitered joins), + * ART_PATH_STROKE_JOIN_ROUND (for round joins), and + * ART_PATH_STROKE_JOIN_BEVEL (for bevelled joins). The mitered join + * is converted to a bevelled join if the miter would extend to a + * distance of more than @miter_limit * @line_width from the actual + * join point. + * + * If there are open subpaths, the ends of these subpaths are capped + * according to the @cap rule. Possible values are + * ART_PATH_STROKE_CAP_BUTT (squared cap, extends exactly to end + * point), ART_PATH_STROKE_CAP_ROUND (rounded half-circle centered at + * the end point), and ART_PATH_STROKE_CAP_SQUARE (squared cap, + * extending half @line_width past the end point). + * + * The @flatness parameter controls the accuracy of the rendering. It + * is most important for determining the number of points to use to + * approximate circular arcs for round lines and joins. In general, the + * resulting vector path will be within @flatness pixels of the "ideal" + * path containing actual circular arcs. I reserve the right to use + * the @flatness parameter to convert bevelled joins to miters for very + * small turn angles, as this would reduce the number of points in the + * resulting outline path. + * + * The resulting path is "clean" with respect to self-intersections, i.e. + * the winding number is 0 or 1 at each point. + * + * Return value: Resulting stroked outline in svp format. + **/ +ArtSVP *art_svp_vpath_stroke(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) { + ArtVpath *vpath_stroke; + ArtSVP *svp, *svp2; + ArtSvpWriter *swr; + + vpath_stroke = art_svp_vpath_stroke_raw(vpath, join, cap, + line_width, miter_limit, flatness); + svp = art_svp_from_vpath(vpath_stroke); + free(vpath_stroke); + + swr = art_svp_writer_rewind_new(ART_WIND_RULE_NONZERO); + art_svp_intersector(svp, swr); + + svp2 = art_svp_writer_rewind_reap(swr); + art_svp_free(svp); + return svp2; +} + + +/* Testbed implementation of the new intersection code. +*/ + +typedef struct _ArtPriQ ArtPriQ; +typedef struct _ArtPriPoint ArtPriPoint; + +struct _ArtPriQ { + int n_items; + int n_items_max; + ArtPriPoint **items; +}; + +struct _ArtPriPoint { + double x; + double y; + void *user_data; +}; + +static ArtPriQ *art_pri_new(void) { + ArtPriQ *result = art_new(ArtPriQ, 1); + + result->n_items = 0; + result->n_items_max = 16; + result->items = art_new(ArtPriPoint *, result->n_items_max); + return result; +} + +static void art_pri_free(ArtPriQ *pq) { + free(pq->items); + free(pq); +} + +static art_boolean art_pri_empty(ArtPriQ *pq) { + return pq->n_items == 0; +} + +/* This heap implementation is based on Vasek Chvatal's course notes: + http://www.cs.rutgers.edu/~chvatal/notes/pq.html#heap */ + +static void art_pri_bubble_up(ArtPriQ *pq, int vacant, ArtPriPoint *missing) { + ArtPriPoint **items = pq->items; + int parent; + + parent = (vacant - 1) >> 1; + while (vacant > 0 && (missing->y < items[parent]->y || + (missing->y == items[parent]->y && + missing->x < items[parent]->x))) { + items[vacant] = items[parent]; + vacant = parent; + parent = (vacant - 1) >> 1; + } + + items[vacant] = missing; +} + +static void art_pri_insert(ArtPriQ *pq, ArtPriPoint *point) { + if (pq->n_items == pq->n_items_max) + art_expand(pq->items, ArtPriPoint *, pq->n_items_max); + + art_pri_bubble_up(pq, pq->n_items++, point); +} + +static void art_pri_sift_down_from_root(ArtPriQ *pq, ArtPriPoint *missing) { + ArtPriPoint **items = pq->items; + int vacant = 0, child = 2; + int n = pq->n_items; + + while (child < n) { + if (items[child - 1]->y < items[child]->y || + (items[child - 1]->y == items[child]->y && + items[child - 1]->x < items[child]->x)) + child--; + items[vacant] = items[child]; + vacant = child; + child = (vacant + 1) << 1; + } + if (child == n) { + items[vacant] = items[n - 1]; + vacant = n - 1; + } + + art_pri_bubble_up(pq, vacant, missing); +} + +static ArtPriPoint *art_pri_choose(ArtPriQ *pq) { + ArtPriPoint *result = pq->items[0]; + + art_pri_sift_down_from_root(pq, pq->items[--pq->n_items]); + return result; +} + +/* A virtual class for an "svp writer". A client of this object creates an + SVP by repeatedly calling "add segment" and "add point" methods on it. +*/ + +typedef struct _ArtSvpWriterRewind ArtSvpWriterRewind; + +/* An implementation of the svp writer virtual class that applies the + winding rule. */ + +struct _ArtSvpWriterRewind { + ArtSvpWriter super; + ArtWindRule rule; + ArtSVP *svp; + int n_segs_max; + int *n_points_max; +}; + +static int art_svp_writer_rewind_add_segment(ArtSvpWriter *self, int wind_left, + int delta_wind, double x, double y) { + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *svp; + ArtSVPSeg *seg; + art_boolean left_filled = 0, right_filled = 0; + int wind_right = wind_left + delta_wind; + int seg_num; + const int init_n_points_max = 4; + + switch (swr->rule) { + case ART_WIND_RULE_NONZERO: + left_filled = (wind_left != 0); + right_filled = (wind_right != 0); + break; + case ART_WIND_RULE_INTERSECT: + left_filled = (wind_left > 1); + right_filled = (wind_right > 1); + break; + case ART_WIND_RULE_ODDEVEN: + left_filled = (wind_left & 1); + right_filled = (wind_right & 1); + break; + case ART_WIND_RULE_POSITIVE: + left_filled = (wind_left > 0); + right_filled = (wind_right > 0); + break; + default: + art_die("Unknown wind rule %d\n", swr->rule); + } + if (left_filled == right_filled) { + /* discard segment now */ + return -1; + } + + svp = swr->svp; + seg_num = svp->n_segs++; + if (swr->n_segs_max == seg_num) { + swr->n_segs_max <<= 1; + svp = (ArtSVP *)realloc(svp, sizeof(ArtSVP) + + (swr->n_segs_max - 1) * + sizeof(ArtSVPSeg)); + swr->svp = svp; + swr->n_points_max = art_renew(swr->n_points_max, int, + swr->n_segs_max); + } + seg = &svp->segs[seg_num]; + seg->n_points = 1; + seg->dir = right_filled; + swr->n_points_max[seg_num] = init_n_points_max; + seg->bbox.x0 = x; + seg->bbox.y0 = y; + seg->bbox.x1 = x; + seg->bbox.y1 = y; + seg->points = art_new(ArtPoint, init_n_points_max); + seg->points[0].x = x; + seg->points[0].y = y; + return seg_num; +} + +static void art_svp_writer_rewind_add_point(ArtSvpWriter *self, int seg_id, + double x, double y) { + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVPSeg *seg; + int n_points; + + if (seg_id < 0) + /* omitted segment */ + return; + + seg = &swr->svp->segs[seg_id]; + n_points = seg->n_points++; + if (swr->n_points_max[seg_id] == n_points) + art_expand(seg->points, ArtPoint, swr->n_points_max[seg_id]); + seg->points[n_points].x = x; + seg->points[n_points].y = y; + if (x < seg->bbox.x0) + seg->bbox.x0 = x; + if (x > seg->bbox.x1) + seg->bbox.x1 = x; + seg->bbox.y1 = y; +} + +static void art_svp_writer_rewind_close_segment(ArtSvpWriter *self, int seg_id) { + /* Not needed for this simple implementation. A potential future + optimization is to merge segments that can be merged safely. */ +} + +ArtSVP *art_svp_writer_rewind_reap(ArtSvpWriter *self) { + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *result = swr->svp; + + free(swr->n_points_max); + free(swr); + return result; +} + +ArtSvpWriter *art_svp_writer_rewind_new(ArtWindRule rule) { + ArtSvpWriterRewind *result = art_new(ArtSvpWriterRewind, 1); + + result->super.add_segment = art_svp_writer_rewind_add_segment; + result->super.add_point = art_svp_writer_rewind_add_point; + result->super.close_segment = art_svp_writer_rewind_close_segment; + + result->rule = rule; + result->n_segs_max = 16; + result->svp = (ArtSVP *)malloc(sizeof(ArtSVP) + + (result->n_segs_max - 1) * sizeof(ArtSVPSeg)); + result->svp->n_segs = 0; + result->n_points_max = art_new(int, result->n_segs_max); + + return &result->super; +} + +/* Now, data structures for the active list */ + +typedef struct _ArtActiveSeg ArtActiveSeg; + +/* Note: BNEG is 1 for \ lines, and 0 for /. Thus, + x[(flags & BNEG) ^ 1] <= x[flags & BNEG] */ +#define ART_ACTIVE_FLAGS_BNEG 1 + +/* This flag is set if the segment has been inserted into the active + list. */ +#define ART_ACTIVE_FLAGS_IN_ACTIVE 2 + +/* This flag is set when the segment is to be deleted in the + horiz commit process. */ +#define ART_ACTIVE_FLAGS_DEL 4 + +/* This flag is set if the seg_id is a valid output segment. */ +#define ART_ACTIVE_FLAGS_OUT 8 + +/* This flag is set if the segment is in the horiz list. */ +#define ART_ACTIVE_FLAGS_IN_HORIZ 16 + +struct _ArtActiveSeg { + int flags; + int wind_left, delta_wind; + ArtActiveSeg *left, *right; /* doubly linked list structure */ + + const ArtSVPSeg *in_seg; + int in_curs; + + double x[2]; + double y0, y1; + double a, b, c; /* line equation; ax+by+c = 0 for the line, a^2 + b^2 = 1, + and a>0 */ + + /* bottom point and intersection point stack */ + int n_stack; + int n_stack_max; + ArtPoint *stack; + + /* horiz commit list */ + ArtActiveSeg *horiz_left, *horiz_right; + double horiz_x; + int horiz_delta_wind; + int seg_id; +}; + +typedef struct _ArtIntersectCtx ArtIntersectCtx; + +struct _ArtIntersectCtx { + const ArtSVP *in; + ArtSvpWriter *out; + + ArtPriQ *pq; + + ArtActiveSeg *active_head; + + double y; + ArtActiveSeg *horiz_first; + ArtActiveSeg *horiz_last; + + /* segment index of next input segment to be added to pri q */ + int in_curs; +}; + +#define EPSILON_A 1e-5 /* Threshold for breaking lines at point insertions */ + +/** + * art_svp_intersect_setup_seg: Set up an active segment from input segment. + * @seg: Active segment. + * @pri_pt: Priority queue point to initialize. + * + * Sets the x[], a, b, c, flags, and stack fields according to the + * line from the current cursor value. Sets the priority queue point + * to the bottom point of this line. Also advances the input segment + * cursor. + **/ +static void art_svp_intersect_setup_seg(ArtActiveSeg *seg, ArtPriPoint *pri_pt) { + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs++; + double x0, y0, x1, y1; + double dx, dy, s; + double a, b, r2; + + x0 = in_seg->points[in_curs].x; + y0 = in_seg->points[in_curs].y; + x1 = in_seg->points[in_curs + 1].x; + y1 = in_seg->points[in_curs + 1].y; + pri_pt->x = x1; + pri_pt->y = y1; + dx = x1 - x0; + dy = y1 - y0; + r2 = dx * dx + dy * dy; + s = r2 == 0 ? 1 : 1 / sqrt(r2); + seg->a = a = dy * s; + seg->b = b = -dx * s; + seg->c = -(a * x0 + b * y0); + seg->flags = (seg->flags & ~ART_ACTIVE_FLAGS_BNEG) | (dx > 0); + seg->x[0] = x0; + seg->x[1] = x1; + seg->y0 = y0; + seg->y1 = y1; + seg->n_stack = 1; + seg->stack[0].x = x1; + seg->stack[0].y = y1; +} + +/** + * art_svp_intersect_add_horiz: Add point to horizontal list. + * @ctx: Intersector context. + * @seg: Segment with point to insert into horizontal list. + * + * Inserts @seg into horizontal list, keeping it in ascending horiz_x + * order. + * + * Note: the horiz_commit routine processes "clusters" of segs in the + * horiz list, all sharing the same horiz_x value. The cluster is + * processed in active list order, rather than horiz list order. Thus, + * the order of segs in the horiz list sharing the same horiz_x + * _should_ be irrelevant. Even so, we use b as a secondary sorting key, + * as a "belt and suspenders" defensive coding tactic. + **/ +static void art_svp_intersect_add_horiz(ArtIntersectCtx *ctx, ArtActiveSeg *seg) { + ArtActiveSeg **pp = &ctx->horiz_last; + ArtActiveSeg *place; + ArtActiveSeg *place_right = NULL; + + if (seg->flags & ART_ACTIVE_FLAGS_IN_HORIZ) { + art_warn("*** attempt to put segment in horiz list twice\n"); + return; + } + seg->flags |= ART_ACTIVE_FLAGS_IN_HORIZ; + + for (place = *pp; place != NULL && (place->horiz_x > seg->horiz_x || + (place->horiz_x == seg->horiz_x && + place->b < seg->b)); + place = *pp) { + place_right = place; + pp = &place->horiz_left; + } + *pp = seg; + seg->horiz_left = place; + seg->horiz_right = place_right; + if (place == NULL) + ctx->horiz_first = seg; + else + place->horiz_right = seg; +} + +static void art_svp_intersect_push_pt(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x, double y) { + ArtPriPoint *pri_pt; + int n_stack = seg->n_stack; + + if (n_stack == seg->n_stack_max) + art_expand(seg->stack, ArtPoint, seg->n_stack_max); + seg->stack[n_stack].x = x; + seg->stack[n_stack].y = y; + seg->n_stack++; + + seg->x[1] = x; + seg->y1 = y; + + pri_pt = art_new(ArtPriPoint, 1); + pri_pt->x = x; + pri_pt->y = y; + pri_pt->user_data = seg; + art_pri_insert(ctx->pq, pri_pt); +} + +typedef enum { + ART_BREAK_LEFT = 1, + ART_BREAK_RIGHT = 2 +} ArtBreakFlags; + +/** + * art_svp_intersect_break: Break an active segment. + * + * Note: y must be greater than the top point's y, and less than + * the bottom's. + * + * Return value: x coordinate of break point. + */ +static double art_svp_intersect_break(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x_ref, double y, ArtBreakFlags break_flags) { + double x0, y0, x1, y1; + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + double x; + + x0 = in_seg->points[in_curs - 1].x; + y0 = in_seg->points[in_curs - 1].y; + x1 = in_seg->points[in_curs].x; + y1 = in_seg->points[in_curs].y; + x = x0 + (x1 - x0) * ((y - y0) / (y1 - y0)); + if ((break_flags == ART_BREAK_LEFT && x > x_ref) || + (break_flags == ART_BREAK_RIGHT && x < x_ref)) { + } + + /* I think we can count on min(x0, x1) <= x <= max(x0, x1) with sane + arithmetic, but it might be worthwhile to check just in case. */ + + if (y > ctx->y) + art_svp_intersect_push_pt(ctx, seg, x, y); + else { + seg->x[0] = x; + seg->y0 = y; + seg->horiz_x = x; + art_svp_intersect_add_horiz(ctx, seg); + } + + return x; +} + +/** + * art_svp_intersect_add_point: Add a point, breaking nearby neighbors. + * @ctx: Intersector context. + * @x: X coordinate of point to add. + * @y: Y coordinate of point to add. + * @seg: "nearby" segment, or NULL if leftmost. + * + * Return value: Segment immediately to the left of the new point, or + * NULL if the new point is leftmost. + **/ +static ArtActiveSeg *art_svp_intersect_add_point(ArtIntersectCtx *ctx, double x, double y, + ArtActiveSeg *seg, ArtBreakFlags break_flags) { + ArtActiveSeg *left, *right; + double x_min = x, x_max = x; + art_boolean left_live, right_live; + double d; + double new_x; + ArtActiveSeg *test, *result = NULL; + double x_test; + + left = seg; + if (left == NULL) + right = ctx->active_head; + else + right = left->right; + left_live = (break_flags & ART_BREAK_LEFT) && (left != NULL); + right_live = (break_flags & ART_BREAK_RIGHT) && (right != NULL); + while (left_live || right_live) { + if (left_live) { + if (x <= left->x[left->flags & ART_ACTIVE_FLAGS_BNEG] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != left->y0 && y < left->y1) { + d = x_min * left->a + y * left->b + left->c; + if (d < EPSILON_A) { + new_x = art_svp_intersect_break(ctx, left, x_min, y, + ART_BREAK_LEFT); + if (new_x > x_max) { + x_max = new_x; + right_live = (right != NULL); + } else if (new_x < x_min) + x_min = new_x; + left = left->left; + left_live = (left != NULL); + } else + left_live = ART_FALSE; + } else + left_live = ART_FALSE; + } else if (right_live) { + if (x >= right->x[(right->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != right->y0 && y < right->y1) { + d = x_max * right->a + y * right->b + right->c; + if (d > -EPSILON_A) { + new_x = art_svp_intersect_break(ctx, right, x_max, y, + ART_BREAK_RIGHT); + if (new_x < x_min) { + x_min = new_x; + left_live = (left != NULL); + } else if (new_x >= x_max) + x_max = new_x; + right = right->right; + right_live = (right != NULL); + } else + right_live = ART_FALSE; + } else + right_live = ART_FALSE; + } + } + + /* Ascending order is guaranteed by break_flags. Thus, we don't need + to actually fix up non-ascending pairs. */ + + /* Now, (left, right) defines an interval of segments broken. Sort + into ascending x order. */ + test = left == NULL ? ctx->active_head : left->right; + result = left; + if (test != NULL && test != right) { + if (y == test->y0) + x_test = test->x[0]; + else /* assert y == test->y1, I think */ + x_test = test->x[1]; + for (;;) { + if (x_test <= x) + result = test; + test = test->right; + if (test == right) + break; + new_x = x_test; + if (new_x < x_test) { + art_warn("art_svp_intersect_add_point: non-ascending x\n"); + } + x_test = new_x; + } + } + return result; +} + +static void art_svp_intersect_swap_active(ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg) { + right_seg->left = left_seg->left; + if (right_seg->left != NULL) + right_seg->left->right = right_seg; + else + ctx->active_head = right_seg; + left_seg->right = right_seg->right; + if (left_seg->right != NULL) + left_seg->right->left = left_seg; + left_seg->left = right_seg; + right_seg->right = left_seg; +} + +/** + * art_svp_intersect_test_cross: Test crossing of a pair of active segments. + * @ctx: Intersector context. + * @left_seg: Left segment of the pair. + * @right_seg: Right segment of the pair. + * @break_flags: Flags indicating whether to break neighbors. + * + * Tests crossing of @left_seg and @right_seg. If there is a crossing, + * inserts the intersection point into both segments. + * + * Return value: True if the intersection took place at the current + * scan line, indicating further iteration is needed. + **/ +static art_boolean art_svp_intersect_test_cross(ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg, + ArtBreakFlags break_flags) { + double left_x0, left_y0, left_x1; + double left_y1 = left_seg->y1; + double right_y1 = right_seg->y1; + double d; + + const ArtSVPSeg *in_seg; + int in_curs; + double d0, d1, t; + double x, y; /* intersection point */ + + if (left_seg->y0 == right_seg->y0 && left_seg->x[0] == right_seg->x[0]) { + /* Top points of left and right segments coincide. This case + feels like a bit of duplication - we may want to merge it + with the cases below. However, this way, we're sure that this + logic makes only localized changes. */ + + if (left_y1 < right_y1) { + /* Test left (x1, y1) against right segment */ + left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) { + /* I'm unsure about the break flags here. */ + double right_x1 = art_svp_intersect_break(ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else if (left_y1 > right_y1) { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) { + /* See above regarding break flags. */ + left_x1 = art_svp_intersect_break(ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else { /* left_y1 == right_y1 */ + left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + art_svp_intersect_swap_active(ctx, left_seg, right_seg); + return ART_TRUE; + } + + if (left_y1 < right_y1) { + /* Test left (x1, y1) against right segment */ + left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) { + double right_x1 = art_svp_intersect_break(ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else if (left_y1 > right_y1) { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) { + left_x1 = art_svp_intersect_break(ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } else { /* left_y1 == right_y1 */ + left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + + /* The segments cross. Find the intersection point. */ + + in_seg = left_seg->in_seg; + in_curs = left_seg->in_curs; + left_x0 = in_seg->points[in_curs - 1].x; + left_y0 = in_seg->points[in_curs - 1].y; + left_x1 = in_seg->points[in_curs].x; + left_y1 = in_seg->points[in_curs].y; + d0 = left_x0 * right_seg->a + left_y0 * right_seg->b + right_seg->c; + d1 = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d0 == d1) { + x = left_x0; + y = left_y0; + } else { + /* Is this division always safe? It could possibly overflow. */ + t = d0 / (d0 - d1); + if (t <= 0) { + x = left_x0; + y = left_y0; + } else if (t >= 1) { + x = left_x1; + y = left_y1; + } else { + x = left_x0 + t * (left_x1 - left_x0); + y = left_y0 + t * (left_y1 - left_y0); + } + } + + /* Make sure intersection point is within bounds of right seg. */ + if (y < right_seg->y0) { + x = right_seg->x[0]; + y = right_seg->y0; + } else if (y > right_seg->y1) { + x = right_seg->x[1]; + y = right_seg->y1; + } else if (x < right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]) + x = right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]; + else if (x > right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]) + x = right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]; + + if (y == left_seg->y0) { + if (y != right_seg->y0) { + art_svp_intersect_push_pt(ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point(ctx, x, y, right_seg->right, + break_flags); + } else { + /* Intersection takes place at current scan line; process + immediately rather than queueing intersection point into + priq. */ + ArtActiveSeg *winner, *loser; + + /* Choose "most vertical" segement */ + if (left_seg->a > right_seg->a) { + winner = left_seg; + loser = right_seg; + } else { + winner = right_seg; + loser = left_seg; + } + + loser->x[0] = winner->x[0]; + loser->horiz_x = loser->x[0]; + loser->horiz_delta_wind += loser->delta_wind; + winner->horiz_delta_wind -= loser->delta_wind; + + art_svp_intersect_swap_active(ctx, left_seg, right_seg); + return ART_TRUE; + } + } else if (y == right_seg->y0) { + art_svp_intersect_push_pt(ctx, left_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point(ctx, x, y, left_seg->left, + break_flags); + } else { + /* Insert the intersection point into both segments. */ + art_svp_intersect_push_pt(ctx, left_seg, x, y); + art_svp_intersect_push_pt(ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point(ctx, x, y, left_seg->left, break_flags); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point(ctx, x, y, right_seg->right, break_flags); + } + return ART_FALSE; +} + +/** + * art_svp_intersect_active_delete: Delete segment from active list. + * @ctx: Intersection context. + * @seg: Segment to delete. + * + * Deletes @seg from the active list. + **/ +static void art_svp_intersect_active_delete(ArtIntersectCtx *ctx, ArtActiveSeg *seg) { + ArtActiveSeg *left = seg->left, *right = seg->right; + + if (left != NULL) + left->right = right; + else + ctx->active_head = right; + if (right != NULL) + right->left = left; +} + +/** + * art_svp_intersect_active_free: Free an active segment. + * @seg: Segment to delete. + * + * Frees @seg. + **/ +static void art_svp_intersect_active_free(ArtActiveSeg *seg) { + free(seg->stack); + free(seg); +} + +/** + * art_svp_intersect_insert_cross: Test crossings of newly inserted line. + * + * Tests @seg against its left and right neighbors for intersections. + * Precondition: the line in @seg is not purely horizontal. + **/ +static void art_svp_intersect_insert_cross(ArtIntersectCtx *ctx, + ArtActiveSeg *seg) { + ArtActiveSeg *left = seg, *right = seg; + + for (;;) { + if (left != NULL) { + ArtActiveSeg *leftc; + + for (leftc = left->left; leftc != NULL; leftc = leftc->left) + if (!(leftc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (leftc != NULL && + art_svp_intersect_test_cross(ctx, leftc, left, + ART_BREAK_LEFT)) { + if (left == right || right == NULL) + right = left->right; + } else { + left = NULL; + } + } else if (right != NULL && right->right != NULL) { + ArtActiveSeg *rightc; + + for (rightc = right->right; rightc != NULL; rightc = rightc->right) + if (!(rightc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (rightc != NULL && + art_svp_intersect_test_cross(ctx, right, rightc, + ART_BREAK_RIGHT)) { + if (left == right || left == NULL) + left = right->left; + } else { + right = NULL; + } + } else + break; + } +} + +/** + * art_svp_intersect_horiz: Add horizontal line segment. + * @ctx: Intersector context. + * @seg: Segment on which to add horizontal line. + * @x0: Old x position. + * @x1: New x position. + * + * Adds a horizontal line from @x0 to @x1, and updates the current + * location of @seg to @x1. + **/ +static void art_svp_intersect_horiz(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x0, double x1) { + ArtActiveSeg *hs; + + if (x0 == x1) + return; + + hs = art_new(ArtActiveSeg, 1); + + hs->flags = ART_ACTIVE_FLAGS_DEL | (seg->flags & ART_ACTIVE_FLAGS_OUT); + if (seg->flags & ART_ACTIVE_FLAGS_OUT) { + ArtSvpWriter *swr = ctx->out; + + swr->add_point(swr, seg->seg_id, x0, ctx->y); + } + hs->seg_id = seg->seg_id; + hs->horiz_x = x0; + hs->horiz_delta_wind = seg->delta_wind; + hs->stack = NULL; + + /* Ideally, the (a, b, c) values will never be read. However, there + are probably some tests remaining that don't check for _DEL + before evaluating the line equation. For those, these + initializations will at least prevent a UMR of the values, which + can crash on some platforms. */ + hs->a = 0.0; + hs->b = 0.0; + hs->c = 0.0; + + seg->horiz_delta_wind -= seg->delta_wind; + + art_svp_intersect_add_horiz(ctx, hs); + + if (x0 > x1) { + ArtActiveSeg *left; + art_boolean first = ART_TRUE; + + for (left = seg->left; left != NULL; left = seg->left) { + int left_bneg = left->flags & ART_ACTIVE_FLAGS_BNEG; + + if (left->x[left_bneg] <= x1) + break; + if (left->x[left_bneg ^ 1] <= x1 && + x1 *left->a + ctx->y *left->b + left->c >= 0) + break; + if (left->y0 != ctx->y && left->y1 != ctx->y) { + art_svp_intersect_break(ctx, left, x1, ctx->y, ART_BREAK_LEFT); + } + art_svp_intersect_swap_active(ctx, left, seg); + if (first && left->right != NULL) { + art_svp_intersect_test_cross(ctx, left, left->right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } else { + ArtActiveSeg *right; + art_boolean first = ART_TRUE; + + for (right = seg->right; right != NULL; right = seg->right) { + int right_bneg = right->flags & ART_ACTIVE_FLAGS_BNEG; + + if (right->x[right_bneg ^ 1] >= x1) + break; + if (right->x[right_bneg] >= x1 && + x1 *right->a + ctx->y *right->b + right->c <= 0) + break; + if (right->y0 != ctx->y && right->y1 != ctx->y) { + art_svp_intersect_break(ctx, right, x1, ctx->y, + ART_BREAK_LEFT); + } + art_svp_intersect_swap_active(ctx, seg, right); + if (first && right->left != NULL) { + art_svp_intersect_test_cross(ctx, right->left, right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } + + seg->x[0] = x1; + seg->x[1] = x1; + seg->horiz_x = x1; + seg->flags &= ~ART_ACTIVE_FLAGS_OUT; +} + +/** + * art_svp_intersect_insert_line: Insert a line into the active list. + * @ctx: Intersector context. + * @seg: Segment containing line to insert. + * + * Inserts the line into the intersector context, taking care of any + * intersections, and adding the appropriate horizontal points to the + * active list. + **/ +static void art_svp_intersect_insert_line(ArtIntersectCtx *ctx, ArtActiveSeg *seg) { + if (seg->y1 == seg->y0) { + art_svp_intersect_horiz(ctx, seg, seg->x[0], seg->x[1]); + } else { + art_svp_intersect_insert_cross(ctx, seg); + art_svp_intersect_add_horiz(ctx, seg); + } +} + +static void art_svp_intersect_process_intersection(ArtIntersectCtx *ctx, + ArtActiveSeg *seg) { + int n_stack = --seg->n_stack; + seg->x[1] = seg->stack[n_stack - 1].x; + seg->y1 = seg->stack[n_stack - 1].y; + seg->x[0] = seg->stack[n_stack].x; + seg->y0 = seg->stack[n_stack].y; + seg->horiz_x = seg->x[0]; + art_svp_intersect_insert_line(ctx, seg); +} + +static void art_svp_intersect_advance_cursor(ArtIntersectCtx *ctx, ArtActiveSeg *seg, + ArtPriPoint *pri_pt) { + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + ArtSvpWriter *swr = seg->flags & ART_ACTIVE_FLAGS_OUT ? ctx->out : NULL; + + if (swr != NULL) + swr->add_point(swr, seg->seg_id, seg->x[1], seg->y1); + if (in_curs + 1 == in_seg->n_points) { + ArtActiveSeg *left = seg->left, *right = seg->right; + + seg->flags |= ART_ACTIVE_FLAGS_DEL; + art_svp_intersect_add_horiz(ctx, seg); + art_svp_intersect_active_delete(ctx, seg); + if (left != NULL && right != NULL) + art_svp_intersect_test_cross(ctx, left, right, + (ArtBreakFlags)(ART_BREAK_LEFT | ART_BREAK_RIGHT)); + free(pri_pt); + } else { + seg->horiz_x = seg->x[1]; + + art_svp_intersect_setup_seg(seg, pri_pt); + art_pri_insert(ctx->pq, pri_pt); + art_svp_intersect_insert_line(ctx, seg); + } +} + +static void art_svp_intersect_add_seg(ArtIntersectCtx *ctx, const ArtSVPSeg *in_seg) { + ArtActiveSeg *seg = art_new(ArtActiveSeg, 1); + ArtActiveSeg *test; + double x0, y0; + ArtActiveSeg *beg_range; + ArtActiveSeg *last = NULL; + ArtActiveSeg *left, *right; + ArtPriPoint *pri_pt = art_new(ArtPriPoint, 1); + + seg->flags = 0; + seg->in_seg = in_seg; + seg->in_curs = 0; + + seg->n_stack_max = 4; + seg->stack = art_new(ArtPoint, seg->n_stack_max); + + seg->horiz_delta_wind = 0; + + seg->wind_left = 0; + + pri_pt->user_data = seg; + art_svp_intersect_setup_seg(seg, pri_pt); + art_pri_insert(ctx->pq, pri_pt); + + /* Find insertion place for new segment */ + /* This is currently a left-to-right scan, but should be replaced + with a binary search as soon as it's validated. */ + + x0 = in_seg->points[0].x; + y0 = in_seg->points[0].y; + beg_range = NULL; + for (test = ctx->active_head; test != NULL; test = test->right) { + double d; + int test_bneg = test->flags & ART_ACTIVE_FLAGS_BNEG; + + if (x0 < test->x[test_bneg]) { + if (x0 < test->x[test_bneg ^ 1]) + break; + d = x0 * test->a + y0 * test->b + test->c; + if (d < 0) + break; + } + last = test; + } + + left = art_svp_intersect_add_point(ctx, x0, y0, last, (ArtBreakFlags)(ART_BREAK_LEFT | ART_BREAK_RIGHT)); + seg->left = left; + if (left == NULL) { + right = ctx->active_head; + ctx->active_head = seg; + } else { + right = left->right; + left->right = seg; + } + seg->right = right; + if (right != NULL) + right->left = seg; + + seg->delta_wind = in_seg->dir ? 1 : -1; + seg->horiz_x = x0; + + art_svp_intersect_insert_line(ctx, seg); +} + +/** + * art_svp_intersect_horiz_commit: Commit points in horiz list to output. + * @ctx: Intersection context. + * + * The main function of the horizontal commit is to output new + * points to the output writer. + * + * This "commit" pass is also where winding numbers are assigned, + * because doing it here provides much greater tolerance for inputs + * which are not in strict SVP order. + * + * Each cluster in the horiz_list contains both segments that are in + * the active list (ART_ACTIVE_FLAGS_DEL is false) and that are not, + * and are scheduled to be deleted (ART_ACTIVE_FLAGS_DEL is true). We + * need to deal with both. + **/ +static void art_svp_intersect_horiz_commit(ArtIntersectCtx *ctx) { + ArtActiveSeg *seg; + int winding_number = 0; /* initialization just to avoid warning */ + int horiz_wind = 0; + double last_x = 0; /* initialization just to avoid warning */ + + /* Output points to svp writer. */ + for (seg = ctx->horiz_first; seg != NULL;) { + /* Find a cluster with common horiz_x, */ + ArtActiveSeg *curs; + double x = seg->horiz_x; + + /* Generate any horizontal segments. */ + if (horiz_wind != 0) { + ArtSvpWriter *swr = ctx->out; + int seg_id; + + seg_id = swr->add_segment(swr, winding_number, horiz_wind, + last_x, ctx->y); + swr->add_point(swr, seg_id, x, ctx->y); + swr->close_segment(swr, seg_id); + } + + /* Find first active segment in cluster. */ + + for (curs = seg; curs != NULL && curs->horiz_x == x; + curs = curs->horiz_right) + if (!(curs->flags & ART_ACTIVE_FLAGS_DEL)) + break; + + if (curs != NULL && curs->horiz_x == x) { + /* There exists at least one active segment in this cluster. */ + + /* Find beginning of cluster. */ + for (; curs->left != NULL; curs = curs->left) + if (curs->left->horiz_x != x) + break; + + if (curs->left != NULL) + winding_number = curs->left->wind_left + curs->left->delta_wind; + else + winding_number = 0; + + do { + if (!(curs->flags & ART_ACTIVE_FLAGS_OUT) || + curs->wind_left != winding_number) { + ArtSvpWriter *swr = ctx->out; + + if (curs->flags & ART_ACTIVE_FLAGS_OUT) { + swr->add_point(swr, curs->seg_id, + curs->horiz_x, ctx->y); + swr->close_segment(swr, curs->seg_id); + } + + curs->seg_id = swr->add_segment(swr, winding_number, + curs->delta_wind, + x, ctx->y); + curs->flags |= ART_ACTIVE_FLAGS_OUT; + } + curs->wind_left = winding_number; + winding_number += curs->delta_wind; + curs = curs->right; + } while (curs != NULL && curs->horiz_x == x); + } + + /* Skip past cluster. */ + do { + ArtActiveSeg *next = seg->horiz_right; + + seg->flags &= ~ART_ACTIVE_FLAGS_IN_HORIZ; + horiz_wind += seg->horiz_delta_wind; + seg->horiz_delta_wind = 0; + if (seg->flags & ART_ACTIVE_FLAGS_DEL) { + if (seg->flags & ART_ACTIVE_FLAGS_OUT) { + ArtSvpWriter *swr = ctx->out; + swr->close_segment(swr, seg->seg_id); + } + art_svp_intersect_active_free(seg); + } + seg = next; + } while (seg != NULL && seg->horiz_x == x); + + last_x = x; + } + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; +} + +void art_svp_intersector(const ArtSVP *in, ArtSvpWriter *out) { + ArtIntersectCtx *ctx; + ArtPriQ *pq; + ArtPriPoint *first_point; + + if (in->n_segs == 0) + return; + + ctx = art_new(ArtIntersectCtx, 1); + ctx->in = in; + ctx->out = out; + pq = art_pri_new(); + ctx->pq = pq; + + ctx->active_head = NULL; + + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; + + ctx->in_curs = 0; + first_point = art_new(ArtPriPoint, 1); + first_point->x = in->segs[0].points[0].x; + first_point->y = in->segs[0].points[0].y; + first_point->user_data = NULL; + ctx->y = first_point->y; + art_pri_insert(pq, first_point); + + while (!art_pri_empty(pq)) { + ArtPriPoint *pri_point = art_pri_choose(pq); + ArtActiveSeg *seg = (ArtActiveSeg *)pri_point->user_data; + + if (ctx->y != pri_point->y) { + art_svp_intersect_horiz_commit(ctx); + ctx->y = pri_point->y; + } + + if (seg == NULL) { + /* Insert new segment from input */ + const ArtSVPSeg *in_seg = &in->segs[ctx->in_curs++]; + art_svp_intersect_add_seg(ctx, in_seg); + if (ctx->in_curs < in->n_segs) { + const ArtSVPSeg *next_seg = &in->segs[ctx->in_curs]; + pri_point->x = next_seg->points[0].x; + pri_point->y = next_seg->points[0].y; + /* user_data is already NULL */ + art_pri_insert(pq, pri_point); + } else + free(pri_point); + } else { + int n_stack = seg->n_stack; + + if (n_stack > 1) { + art_svp_intersect_process_intersection(ctx, seg); + free(pri_point); + } else { + art_svp_intersect_advance_cursor(ctx, seg, pri_point); + } + } + } + + art_svp_intersect_horiz_commit(ctx); + + art_pri_free(pq); + free(ctx); +} + + +/* The spiffy antialiased renderer for sorted vector paths. */ + +typedef double artfloat; + +struct _ArtSVPRenderAAIter { + const ArtSVP *svp; + int x0, x1; + int y; + int seg_ix; + + int *active_segs; + int n_active_segs; + int *cursor; + artfloat *seg_x; + artfloat *seg_dx; + + ArtSVPRenderAAStep *steps; +}; + +static void art_svp_render_insert_active(int i, int *active_segs, int n_active_segs, + artfloat *seg_x, artfloat *seg_dx) { + int j; + artfloat x; + int tmp1, tmp2; + + /* this is a cheap hack to get ^'s sorted correctly */ + x = seg_x[i] + 0.001 * seg_dx[i]; + for (j = 0; j < n_active_segs && seg_x[active_segs[j]] < x; j++) + ; + + tmp1 = i; + while (j < n_active_segs) { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + j++; + } + active_segs[j] = tmp1; +} + +static void art_svp_render_delete_active(int *active_segs, int j, int n_active_segs) { + int k; + + for (k = j; k < n_active_segs; k++) + active_segs[k] = active_segs[k + 1]; +} + +/* Render the sorted vector path in the given rectangle, antialiased. + + This interface uses a callback for the actual pixel rendering. The + callback is called y1 - y0 times (once for each scan line). The y + coordinate is given as an argument for convenience (it could be + stored in the callback's private data and incremented on each + call). + + The rendered polygon is represented in a semi-runlength format: a + start value and a sequence of "steps". Each step has an x + coordinate and a value delta. The resulting value at position x is + equal to the sum of the start value and all step delta values for + which the step x coordinate is less than or equal to x. An + efficient algorithm will traverse the steps left to right, keeping + a running sum. + + All x coordinates in the steps are guaranteed to be x0 <= x < x1. + (This guarantee is a change from the gfonted vpaar renderer, and is + designed to simplify the callback). + + There is now a further guarantee that no two steps will have the + same x value. This may allow for further speedup and simplification + of renderers. + + The value 0x8000 represents 0% coverage by the polygon, while + 0xff8000 represents 100% coverage. This format is designed so that + >> 16 results in a standard 0x00..0xff value range, with nice + rounding. + + Status of this routine: + + Basic correctness: OK + + Numerical stability: pretty good, although probably not + bulletproof. + + Speed: Needs more aggressive culling of bounding boxes. Can + probably speed up the [x0,x1) clipping of step values. Can do more + of the step calculation in fixed point. + + Precision: No known problems, although it should be tested + thoroughly, especially for symmetry. + +*/ + +ArtSVPRenderAAIter *art_svp_render_aa_iter(const ArtSVP *svp, + int x0, int y0, int x1, int y1) { + ArtSVPRenderAAIter *iter = art_new(ArtSVPRenderAAIter, 1); + + iter->svp = svp; + iter->y = y0; + iter->x0 = x0; + iter->x1 = x1; + iter->seg_ix = 0; + + iter->active_segs = art_new(int, svp->n_segs); + iter->cursor = art_new(int, svp->n_segs); + iter->seg_x = art_new(artfloat, svp->n_segs); + iter->seg_dx = art_new(artfloat, svp->n_segs); + iter->steps = art_new(ArtSVPRenderAAStep, x1 - x0); + iter->n_active_segs = 0; + + return iter; +} + +#define ADD_STEP(xpos, xdelta) \ + /* stereotype code fragment for adding a step */ \ + if (n_steps == 0 || steps[n_steps - 1].x < xpos) \ + { \ + sx = n_steps; \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + else \ + { \ + for (sx = n_steps; sx > 0; sx--) \ + { \ + if (steps[sx - 1].x == xpos) \ + { \ + steps[sx - 1].delta += xdelta; \ + sx = n_steps; \ + break; \ + } \ + else if (steps[sx - 1].x < xpos) \ + { \ + break; \ + } \ + } \ + if (sx < n_steps) \ + { \ + memmove (&steps[sx + 1], &steps[sx], \ + (n_steps - sx) * sizeof(steps[0])); \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + } + +void art_svp_render_aa_iter_step(ArtSVPRenderAAIter *iter, int *p_start, + ArtSVPRenderAAStep **p_steps, int *p_n_steps) { + const ArtSVP *svp = iter->svp; + int *active_segs = iter->active_segs; + int n_active_segs = iter->n_active_segs; + int *cursor = iter->cursor; + artfloat *seg_x = iter->seg_x; + artfloat *seg_dx = iter->seg_dx; + int i = iter->seg_ix; + int j; + int x0 = iter->x0; + int x1 = iter->x1; + int y = iter->y; + int seg_index; + + int x; + ArtSVPRenderAAStep *steps = iter->steps; + int n_steps; + artfloat y_top, y_bot; + artfloat x_top, x_bot; + artfloat x_min, x_max; + int ix_min, ix_max; + artfloat delta; /* delta should be int too? */ + int last, this_; + int xdelta; + artfloat rslope, drslope; + int start; + const ArtSVPSeg *seg; + int curs; + artfloat dy; + + int sx; + + /* insert new active segments */ + for (; i < svp->n_segs && svp->segs[i].bbox.y0 < y + 1; i++) { + if (svp->segs[i].bbox.y1 > y && + svp->segs[i].bbox.x0 < x1) { + seg = &svp->segs[i]; + /* move cursor to topmost vector which overlaps [y,y+1) */ + for (curs = 0; seg->points[curs + 1].y < y; curs++) + ; + cursor[i] = curs; + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs(dy) >= EPSILON_6) + seg_dx[i] = (seg->points[curs + 1].x - seg->points[curs].x) / + dy; + else + seg_dx[i] = 1e12; + seg_x[i] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[i]; + art_svp_render_insert_active(i, active_segs, n_active_segs++, + seg_x, seg_dx); + } + } + + n_steps = 0; + + /* render the runlengths, advancing and deleting as we go */ + start = 0x8000; + + for (j = 0; j < n_active_segs; j++) { + seg_index = active_segs[j]; + seg = &svp->segs[seg_index]; + curs = cursor[seg_index]; + while (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) { + y_top = y; + if (y_top < seg->points[curs].y) + y_top = seg->points[curs].y; + y_bot = y + 1; + if (y_bot > seg->points[curs + 1].y) + y_bot = seg->points[curs + 1].y; + if (y_top != y_bot) { + delta = (seg->dir ? 16711680.0 : -16711680.0) * + (y_bot - y_top); + x_top = seg_x[seg_index] + (y_top - y) * seg_dx[seg_index]; + x_bot = seg_x[seg_index] + (y_bot - y) * seg_dx[seg_index]; + if (x_top < x_bot) { + x_min = x_top; + x_max = x_bot; + } else { + x_min = x_bot; + x_max = x_top; + } + ix_min = (int)floor(x_min); + ix_max = (int)floor(x_max); + if (ix_min >= x1) { + /* skip; it starts to the right of the render region */ + } else if (ix_max < x0) + /* it ends to the left of the render region */ + start += (int)delta; + else if (ix_min == ix_max) { + /* case 1, antialias a single pixel */ + xdelta = (int)((ix_min + 1 - (x_min + x_max) * 0.5) * delta); + + ADD_STEP(ix_min, xdelta) + + if (ix_min + 1 < x1) { + xdelta = (int)(delta - xdelta); + + ADD_STEP(ix_min + 1, xdelta) + } + } else { + /* case 2, antialias a run */ + rslope = 1.0 / fabs(seg_dx[seg_index]); + drslope = delta * rslope; + last = + (int)(drslope * 0.5 * + (ix_min + 1 - x_min) * (ix_min + 1 - x_min)); + xdelta = last; + if (ix_min >= x0) { + ADD_STEP(ix_min, xdelta) + + x = ix_min + 1; + } else { + start += last; + x = x0; + } + if (ix_max > x1) + ix_max = x1; + for (; x < ix_max; x++) { + this_ = (int)((seg->dir ? 16711680.0 : -16711680.0) * rslope * + (x + 0.5 - x_min)); + xdelta = this_ - last; + last = this_; + + ADD_STEP(x, xdelta) + } + if (x < x1) { + this_ = + (int)(delta * (1 - 0.5 * + (x_max - ix_max) * (x_max - ix_max) * + rslope)); + xdelta = this_ - last; + last = this_; + + ADD_STEP(x, xdelta) + + if (x + 1 < x1) { + xdelta = (int)(delta - last); + + ADD_STEP(x + 1, xdelta) + } + } + } + } + curs++; + if (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) { + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs(dy) >= EPSILON_6) + seg_dx[seg_index] = (seg->points[curs + 1].x - + seg->points[curs].x) / dy; + else + seg_dx[seg_index] = 1e12; + seg_x[seg_index] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[seg_index]; + } + /* break here, instead of duplicating predicate in while? */ + } + if (seg->points[curs].y >= y + 1) { + curs--; + cursor[seg_index] = curs; + seg_x[seg_index] += seg_dx[seg_index]; + } else { + art_svp_render_delete_active(active_segs, j--, + --n_active_segs); + } + } + + *p_start = start; + *p_steps = steps; + *p_n_steps = n_steps; + + iter->seg_ix = i; + iter->n_active_segs = n_active_segs; + iter->y++; +} + +void art_svp_render_aa_iter_done(ArtSVPRenderAAIter *iter) { + free(iter->steps); + + free(iter->seg_dx); + free(iter->seg_x); + free(iter->cursor); + free(iter->active_segs); + free(iter); +} + +/** + * art_svp_render_aa: Render SVP antialiased. + * @svp: The #ArtSVP to render. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @callback: The callback which actually paints the pixels. + * @callback_data: Private data for @callback. + * + * Renders the sorted vector path in the given rectangle, antialiased. + * + * This interface uses a callback for the actual pixel rendering. The + * callback is called @y1 - @y0 times (once for each scan line). The y + * coordinate is given as an argument for convenience (it could be + * stored in the callback's private data and incremented on each + * call). + * + * The rendered polygon is represented in a semi-runlength format: a + * start value and a sequence of "steps". Each step has an x + * coordinate and a value delta. The resulting value at position x is + * equal to the sum of the start value and all step delta values for + * which the step x coordinate is less than or equal to x. An + * efficient algorithm will traverse the steps left to right, keeping + * a running sum. + * + * All x coordinates in the steps are guaranteed to be @x0 <= x < @x1. + * (This guarantee is a change from the gfonted vpaar renderer from + * which this routine is derived, and is designed to simplify the + * callback). + * + * The value 0x8000 represents 0% coverage by the polygon, while + * 0xff8000 represents 100% coverage. This format is designed so that + * >> 16 results in a standard 0x00..0xff value range, with nice + * rounding. + * + **/ +void art_svp_render_aa(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + void (*callback)(void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps), + void *callback_data) { + ArtSVPRenderAAIter *iter; + int y; + int start; + ArtSVPRenderAAStep *steps; + int n_steps; + + iter = art_svp_render_aa_iter(svp, x0, y0, x1, y1); + + + for (y = y0; y < y1; y++) { + art_svp_render_aa_iter_step(iter, &start, &steps, &n_steps); + (*callback)(callback_data, y, start, steps, n_steps); + } + + art_svp_render_aa_iter_done(iter); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/art.h b/engines/sword25/gfx/image/art.h new file mode 100644 index 0000000000..90baa770cf --- /dev/null +++ b/engines/sword25/gfx/image/art.h @@ -0,0 +1,279 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Libart_LGPL - library of basic graphic primitives + * + * Copyright (c) 1998 Raph Levien + * + * Licensed under GNU LGPL v2 + * + */ + +/* Simple macros to set up storage allocation and basic types for libart + functions. */ + +#ifndef __ART_H__ +#define __ART_H__ + +#include "common/scummsys.h" + +namespace Sword25 { + +typedef byte art_u8; +typedef uint16 art_u16; +typedef uint32 art_u32; + +/* These aren't, strictly speaking, configuration macros, but they're + damn handy to have around, and may be worth playing with for + debugging. */ +#define art_new(type, n) ((type *)malloc ((n) * sizeof(type))) + +#define art_renew(p, type, n) ((type *)realloc (p, (n) * sizeof(type))) + +/* This one must be used carefully - in particular, p and max should + be variables. They can also be pstruct->el lvalues. */ +#define art_expand(p, type, max) do { if(max) { p = art_renew (p, type, max <<= 1); } else { max = 1; p = art_new(type, 1); } } while (0) + +typedef int art_boolean; +#define ART_FALSE 0 +#define ART_TRUE 1 + +/* define pi */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif /* M_PI */ + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif /* M_SQRT2 */ + +/* Provide macros to feature the GCC function attribute. + */ +#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)) +#define ART_GNUC_PRINTF( format_idx, arg_idx ) \ + __attribute__((__format__ (__printf__, format_idx, arg_idx))) +#define ART_GNUC_NORETURN \ + __attribute__((__noreturn__)) +#else /* !__GNUC__ */ +#define ART_GNUC_PRINTF( format_idx, arg_idx ) +#define ART_GNUC_NORETURN +#endif /* !__GNUC__ */ + +void ART_GNUC_NORETURN +art_die(const char *fmt, ...) ART_GNUC_PRINTF(1, 2); + +void +art_warn(const char *fmt, ...) ART_GNUC_PRINTF(1, 2); + +typedef struct _ArtDRect ArtDRect; +typedef struct _ArtIRect ArtIRect; + +struct _ArtDRect { + /*< public >*/ + double x0, y0, x1, y1; +}; + +struct _ArtIRect { + /*< public >*/ + int x0, y0, x1, y1; +}; + +typedef struct _ArtPoint ArtPoint; + +struct _ArtPoint { + /*< public >*/ + double x, y; +}; + +/* Basic data structures and constructors for sorted vector paths */ + +typedef struct _ArtSVP ArtSVP; +typedef struct _ArtSVPSeg ArtSVPSeg; + +struct _ArtSVPSeg { + int n_points; + int dir; /* == 0 for "up", 1 for "down" */ + ArtDRect bbox; + ArtPoint *points; +}; + +struct _ArtSVP { + int n_segs; + ArtSVPSeg segs[1]; +}; + +void +art_svp_free(ArtSVP *svp); + +int +art_svp_seg_compare(const void *s1, const void *s2); + +/* Basic data structures and constructors for bezier paths */ + +typedef enum { + ART_MOVETO, + ART_MOVETO_OPEN, + ART_CURVETO, + ART_LINETO, + ART_END +} ArtPathcode; + +typedef struct _ArtBpath ArtBpath; + +struct _ArtBpath { + /*< public >*/ + ArtPathcode code; + double x1; + double y1; + double x2; + double y2; + double x3; + double y3; +}; + +/* Basic data structures and constructors for simple vector paths */ + +typedef struct _ArtVpath ArtVpath; + +/* CURVETO is not allowed! */ +struct _ArtVpath { + ArtPathcode code; + double x; + double y; +}; + +/* Some of the functions need to go into their own modules */ + +void +art_vpath_add_point(ArtVpath **p_vpath, int *pn_points, int *pn_points_max, + ArtPathcode code, double x, double y); + +ArtVpath *art_bez_path_to_vec(const ArtBpath *bez, double flatness); + +/* The funky new SVP intersector. */ + +#ifndef ART_WIND_RULE_DEFINED +#define ART_WIND_RULE_DEFINED +typedef enum { + ART_WIND_RULE_NONZERO, + ART_WIND_RULE_INTERSECT, + ART_WIND_RULE_ODDEVEN, + ART_WIND_RULE_POSITIVE +} ArtWindRule; +#endif + +typedef struct _ArtSvpWriter ArtSvpWriter; + +struct _ArtSvpWriter { + int (*add_segment)(ArtSvpWriter *self, int wind_left, int delta_wind, + double x, double y); + void (*add_point)(ArtSvpWriter *self, int seg_id, double x, double y); + void (*close_segment)(ArtSvpWriter *self, int seg_id); +}; + +ArtSvpWriter * +art_svp_writer_rewind_new(ArtWindRule rule); + +ArtSVP * +art_svp_writer_rewind_reap(ArtSvpWriter *self); + +int +art_svp_seg_compare(const void *s1, const void *s2); + +void +art_svp_intersector(const ArtSVP *in, ArtSvpWriter *out); + + +/* Sort vector paths into sorted vector paths. */ + +ArtSVP * +art_svp_from_vpath(ArtVpath *vpath); + +/* Sort vector paths into sorted vector paths. */ + +typedef enum { + ART_PATH_STROKE_JOIN_MITER, + ART_PATH_STROKE_JOIN_ROUND, + ART_PATH_STROKE_JOIN_BEVEL +} ArtPathStrokeJoinType; + +typedef enum { + ART_PATH_STROKE_CAP_BUTT, + ART_PATH_STROKE_CAP_ROUND, + ART_PATH_STROKE_CAP_SQUARE +} ArtPathStrokeCapType; + +ArtSVP * +art_svp_vpath_stroke(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness); + +/* This version may have winding numbers exceeding 1. */ +ArtVpath * +art_svp_vpath_stroke_raw(ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness); + + +/* The spiffy antialiased renderer for sorted vector paths. */ + +typedef struct _ArtSVPRenderAAStep ArtSVPRenderAAStep; +typedef struct _ArtSVPRenderAAIter ArtSVPRenderAAIter; + +struct _ArtSVPRenderAAStep { + int x; + int delta; /* stored with 16 fractional bits */ +}; + +ArtSVPRenderAAIter * +art_svp_render_aa_iter(const ArtSVP *svp, + int x0, int y0, int x1, int y1); + +void +art_svp_render_aa_iter_step(ArtSVPRenderAAIter *iter, int *p_start, + ArtSVPRenderAAStep **p_steps, int *p_n_steps); + +void +art_svp_render_aa_iter_done(ArtSVPRenderAAIter *iter); + +void +art_svp_render_aa(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + void (*callback)(void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps), + void *callback_data); + +} // End of namespace Sword25 + +#endif /* __ART_H__ */ diff --git a/engines/sword25/gfx/image/image.h b/engines/sword25/gfx/image/image.h new file mode 100644 index 0000000000..5ac6d1ac25 --- /dev/null +++ b/engines/sword25/gfx/image/image.h @@ -0,0 +1,222 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + BS_Image + -------- + + Autor: Malte Thiesen +*/ + +#ifndef SWORD25_IMAGE_H +#define SWORD25_IMAGE_H + +// Includes +#include "sword25/kernel/common.h" +#include "common/rect.h" +#include "sword25/gfx/graphicengine.h" + +namespace Sword25 { + +class Image { +public: + virtual ~Image() {} + + // Enums + /** + @brief Die möglichen Flippingparameter für die Blit-Methode. + */ + enum FLIP_FLAGS { + /// Das Bild wird nicht gespiegelt. + FLIP_NONE = 0, + /// Das Bild wird an der horizontalen Achse gespiegelt. + FLIP_H = 1, + /// Das Bild wird an der vertikalen Achse gespiegelt. + FLIP_V = 2, + /// Das Bild wird an der horizontalen und vertikalen Achse gespiegelt. + FLIP_HV = FLIP_H | FLIP_V, + /// Das Bild wird an der horizontalen und vertikalen Achse gespiegelt. + FLIP_VH = FLIP_H | FLIP_V + }; + + //@{ + /** @name Accessor-Methoden */ + + /** + @brief Gibt die Breite des Bildes in Pixeln zurück + */ + virtual int getWidth() const = 0; + + /** + @brief Gibt die Höhe des Bildes in Pixeln zurück + */ + virtual int getHeight() const = 0; + + /** + @brief Gibt das Farbformat des Bildes zurück + */ + virtual GraphicEngine::COLOR_FORMATS getColorFormat() const = 0; + + //@} + + //@{ + /** @name Render-Methoden */ + + /** + @brief Rendert das Bild in den Framebuffer. + @param pDest ein Pointer auf das Zielbild. In den meisten Fällen ist dies der Framebuffer. + @param PosX die Position auf der X-Achse im Zielbild in Pixeln, an der das Bild gerendert werden soll.<br> + Der Standardwert ist 0. + @param PosY die Position auf der Y-Achse im Zielbild in Pixeln, an der das Bild gerendert werden soll.<br> + Der Standardwert ist 0. + @param Flipping gibt an, wie das Bild gespiegelt werden soll.<br> + Der Standardwert ist BS_Image::FLIP_NONE (keine Spiegelung) + @param pSrcPartRect Pointer auf ein Common::Rect, welches den Ausschnitt des Quellbildes spezifiziert, der gerendert + werden soll oder NULL, falls das gesamte Bild gerendert werden soll.<br> + Dieser Ausschnitt bezieht sich auf das ungespiegelte und unskalierte Bild.<br> + Der Standardwert ist NULL. + @param Color ein ARGB Farbwert, der die Parameter für die Farbmodulation und fürs Alphablending festlegt.<br> + Die Alpha-Komponente der Farbe bestimmt den Alphablending Parameter (0 = keine Deckung, 255 = volle Deckung).<br> + Die Farbkomponenten geben die Farbe für die Farbmodulation an.<br> + Der Standardwert is BS_ARGB(255, 255, 255, 255) (volle Deckung, keine Farbmodulation). + Zum Erzeugen des Farbwertes können die Makros BS_RGB und BS_ARGB benutzt werden. + @param Width gibt die Ausgabebreite des Bildausschnittes an. + Falls diese von der Breite des Bildausschnittes abweicht wird + das Bild entsprechend Skaliert.<br> + Der Wert -1 gibt an, dass das Bild nicht Skaliert werden soll.<br> + Der Standardwert ist -1. + @param Width gibt die Ausgabehöhe des Bildausschnittes an. + Falls diese von der Höhe des Bildauschnittes abweicht, wird + das Bild entsprechend Skaliert.<br> + Der Wert -1 gibt an, dass das Bild nicht Skaliert werden soll.<br> + Der Standardwert ist -1. + @return Gibt false zurück, falls das Rendern fehlgeschlagen ist. + @remark Er werden nicht alle Blitting-Operationen von allen BS_Image-Klassen unterstützt.<br> + Mehr Informationen gibt es in der Klassenbeschreibung von BS_Image und durch folgende Methoden: + - IsBlitTarget() + - IsScalingAllowed() + - IsFillingAllowed() + - IsAlphaAllowed() + - IsColorModulationAllowed() + - IsSetContentAllowed() + */ + virtual bool blit(int posX = 0, int posY = 0, + int flipping = FLIP_NONE, + Common::Rect *pPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1) = 0; + + /** + @brief Füllt einen Rechteckigen Bereich des Bildes mit einer Farbe. + @param pFillRect Pointer auf ein Common::Rect, welches den Ausschnitt des Bildes spezifiziert, der gefüllt + werden soll oder NULL, falls das gesamte Bild gefüllt werden soll.<br> + Der Standardwert ist NULL. + @param Color der 32 Bit Farbwert mit dem der Bildbereich gefüllt werden soll. + @remark Es ist möglich über die Methode transparente Rechtecke darzustellen, indem man eine Farbe mit einem Alphawert ungleich + 255 angibt. + @remark Unabhängig vom Farbformat des Bildes muss ein 32 Bit Farbwert angegeben werden. Zur Erzeugung, können die Makros + BS_RGB und BS_ARGB benutzt werden. + @remark Falls das Rechteck nicht völlig innerhalb des Bildschirms ist, wird es automatisch zurechtgestutzt. + */ + virtual bool fill(const Common::Rect *pFillRect = 0, uint color = BS_RGB(0, 0, 0)) = 0; + + /** + @brief Füllt den Inhalt des Bildes mit Pixeldaten. + @param Pixeldata ein Vector der die Pixeldaten enthält. Sie müssen in dem Farbformat des Bildes vorliegen und es müssen genügend Daten + vorhanden sein, um das ganze Bild zu füllen. + @param Offset der Offset in Byte im Pixeldata-Vector an dem sich der erste zu schreibende Pixel befindet.<br> + Der Standardwert ist 0. + @param Stride der Abstand in Byte zwischen dem Zeilenende und dem Beginn einer neuen Zeile im Pixeldata-Vector.<br> + Der Standardwert ist 0. + @return Gibt false zurück, falls der Aufruf fehlgeschlagen ist. + @remark Ein Aufruf dieser Methode ist nur erlaubt, wenn IsSetContentAllowed() true zurückgibt. + */ + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride) = 0; + + /** + @brief Liest einen Pixel des Bildes. + @param X die X-Koordinate des Pixels. + @param Y die Y-Koordinate des Pixels + @return Gibt den 32-Bit Farbwert des Pixels an der übergebenen Koordinate zurück. + @remark Diese Methode sollte auf keine Fall benutzt werden um größere Teile des Bildes zu lesen, da sie sehr langsam ist. Sie ist + eher dafür gedacht einzelne Pixel des Bildes auszulesen. + */ + virtual uint getPixel(int x, int y) = 0; + + //@{ + /** @name Auskunfts-Methoden */ + + /** + @brief Überprüft, ob an dem BS_Image Blit() aufgerufen werden darf. + @return Gibt false zurück, falls ein Blit()-Aufruf an diesem Objekt nicht gestattet ist. + */ + virtual bool isBlitSource() const = 0; + + /** + @brief Überprüft, ob das BS_Image ein Zielbild für einen Blit-Aufruf sein kann. + @return Gibt false zurück, falls ein Blit-Aufruf mit diesem Objekt als Ziel nicht gestattet ist. + */ + virtual bool isBlitTarget() const = 0; + + /** + @brief Gibt true zurück, falls das BS_Image bei einem Aufruf von Blit() skaliert dargestellt werden kann. + */ + virtual bool isScalingAllowed() const = 0; + + /** + @brief Gibt true zurück, wenn das BS_Image mit einem Aufruf von Fill() gefüllt werden kann. + */ + virtual bool isFillingAllowed() const = 0; + + /** + @brief Gibt true zurück, wenn das BS_Image bei einem Aufruf von Blit() mit einem Alphawert dargestellt werden kann. + */ + virtual bool isAlphaAllowed() const = 0; + + /** + @brief Gibt true zurück, wenn das BS_Image bei einem Aufruf von Blit() mit Farbmodulation dargestellt werden kann. + */ + virtual bool isColorModulationAllowed() const = 0; + + /** + @brief Gibt true zurück, wenn der Inhalt des BS_Image durch eine Aufruf von SetContent() ausgetauscht werden kann. + */ + virtual bool isSetContentAllowed() const = 0; + + //@} +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/pngloader.cpp b/engines/sword25/gfx/image/pngloader.cpp new file mode 100644 index 0000000000..1b72595a8f --- /dev/null +++ b/engines/sword25/gfx/image/pngloader.cpp @@ -0,0 +1,255 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// Disable symbol overrides so that we can use png.h +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "sword25/gfx/image/image.h" +#include "sword25/gfx/image/pngloader.h" +#include <png.h> + +namespace Sword25 { + +#define BS_LOG_PREFIX "PNGLOADER" + + +/** + * Load a NULL-terminated string from the given stream. + */ +static Common::String loadString(Common::ReadStream &in, uint maxSize = 999) { + Common::String result; + + while (!in.eos() && (result.size() < maxSize)) { + char ch = (char)in.readByte(); + if (ch == '\0') + break; + + result += ch; + } + + return result; +} + +/** + * Check if the given data is a savegame, and if so, locate the + * offset to the image data. + * @return offset to image data if fileDataPtr contains a savegame; 0 otherwise + */ +static uint findEmbeddedPNG(const byte *fileDataPtr, uint fileSize) { + if (fileSize < 100) + return 0; + if (memcmp(fileDataPtr, "BS25SAVEGAME", 12)) + return 0; + + // Read in the header + Common::MemoryReadStream stream(fileDataPtr, fileSize); + stream.seek(0, SEEK_SET); + + // Headerinformationen der Spielstandes einlesen. + uint compressedGamedataSize; + loadString(stream); // Marker + loadString(stream); // Version + loadString(stream); // Description + Common::String gameSize = loadString(stream); + compressedGamedataSize = atoi(gameSize.c_str()); + loadString(stream); + + // Return the offset of where the thumbnail starts + return static_cast<uint>(stream.pos() + compressedGamedataSize); +} + +static void png_user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { + const byte **ref = (const byte **)png_get_io_ptr(png_ptr); + memcpy(data, *ref, length); + *ref += length; +} + +static bool doIsCorrectImageFormat(const byte *fileDataPtr, uint fileSize) { + return (fileSize > 8) && png_check_sig(const_cast<byte *>(fileDataPtr), 8); +} + + +bool PNGLoader::doDecodeImage(const byte *fileDataPtr, uint fileSize, byte *&uncompressedDataPtr, int &width, int &height, int &pitch) { + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + + int bitDepth; + int colorType; + int interlaceType; + int i; + + // Check for valid PNG signature + if (!doIsCorrectImageFormat(fileDataPtr, fileSize)) { + error("png_check_sig failed"); + } + + // Die beiden PNG Strukturen erstellen + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { + error("Could not create libpng read struct."); + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + error("Could not create libpng info struct."); + } + + // Alternative Lesefunktion benutzen + const byte **ref = &fileDataPtr; + png_set_read_fn(png_ptr, (void *)ref, png_user_read_data); + + // PNG Header einlesen + png_read_info(png_ptr, info_ptr); + + // PNG Informationen auslesen + png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *)&width, (png_uint_32 *)&height, &bitDepth, &colorType, &interlaceType, NULL, NULL); + + // Pitch des Ausgabebildes berechnen + pitch = GraphicEngine::calcPitch(GraphicEngine::CF_ARGB32, width); + + // Speicher für die endgültigen Bilddaten reservieren + // Dieses geschieht vor dem reservieren von Speicher für temporäre Bilddaten um die Fragmentierung des Speichers gering zu halten + uncompressedDataPtr = new byte[pitch * height]; + if (!uncompressedDataPtr) { + error("Could not allocate memory for output image."); + } + + // Bilder jeglicher Farbformate werden zunächst in ARGB Bilder umgewandelt + if (bitDepth == 16) + png_set_strip_16(png_ptr); + if (colorType == PNG_COLOR_TYPE_PALETTE) + png_set_expand(png_ptr); + if (bitDepth < 8) + png_set_expand(png_ptr); + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_expand(png_ptr); + if (colorType == PNG_COLOR_TYPE_GRAY || + colorType == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + png_set_bgr(png_ptr); + + if (colorType != PNG_COLOR_TYPE_RGB_ALPHA) + png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + + // Nachdem die Transformationen registriert wurden, werden die Bilddaten erneut eingelesen + png_read_update_info(png_ptr, info_ptr); + png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *)&width, (png_uint_32 *)&height, &bitDepth, &colorType, NULL, NULL, NULL); + + if (interlaceType == PNG_INTERLACE_NONE) { + // PNGs without interlacing can simply be read row by row. + for (i = 0; i < height; i++) { + png_read_row(png_ptr, uncompressedDataPtr + i * pitch, NULL); + } + } else { + // PNGs with interlacing require us to allocate an auxillary + // buffer with pointers to all row starts. + + // Allocate row pointer buffer + png_bytep *pRowPtr = new png_bytep[height]; + if (!pRowPtr) { + error("Could not allocate memory for row pointers."); + } + + // Initialize row pointers + for (i = 0; i < height; i++) + pRowPtr[i] = uncompressedDataPtr + i * pitch; + + // Read image data + png_read_image(png_ptr, pRowPtr); + + // Free row pointer buffer + delete[] pRowPtr; + } + + // Read additional data at the end. + png_read_end(png_ptr, NULL); + + // Destroy libpng structures + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + // Signal success + return true; +} + +bool PNGLoader::decodeImage(const byte *fileDataPtr, uint fileSize, byte *&uncompressedDataPtr, int &width, int &height, int &pitch) { + uint pngOffset = findEmbeddedPNG(fileDataPtr, fileSize); + return doDecodeImage(fileDataPtr + pngOffset, fileSize - pngOffset, uncompressedDataPtr, width, height, pitch); +} + +bool PNGLoader::doImageProperties(const byte *fileDataPtr, uint fileSize, int &width, int &height) { + // Check for valid PNG signature + if (!doIsCorrectImageFormat(fileDataPtr, fileSize)) + return false; + + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + + // Die beiden PNG Strukturen erstellen + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { + error("Could not create libpng read struct."); + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + error("Could not create libpng info struct."); + } + + // Alternative Lesefunktion benutzen + const byte **ref = &fileDataPtr; + png_set_read_fn(png_ptr, (void *)ref, png_user_read_data); + + // PNG Header einlesen + png_read_info(png_ptr, info_ptr); + + // PNG Informationen auslesen + int bitDepth; + int colorType; + png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *)&width, (png_uint_32 *)&height, &bitDepth, &colorType, NULL, NULL, NULL); + + // Die Strukturen freigeben + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + return true; + +} + +bool PNGLoader::imageProperties(const byte *fileDataPtr, uint fileSize, int &width, int &height) { + uint pngOffset = findEmbeddedPNG(fileDataPtr, fileSize); + return doImageProperties(fileDataPtr + pngOffset, fileSize - pngOffset, width, height); +} + + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/pngloader.h b/engines/sword25/gfx/image/pngloader.h new file mode 100644 index 0000000000..e0d68ff8b9 --- /dev/null +++ b/engines/sword25/gfx/image/pngloader.h @@ -0,0 +1,93 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_PNGLOADER2_H +#define SWORD25_PNGLOADER2_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/graphicengine.h" + +namespace Sword25 { + +/** + * Class for loading PNG files, and PNG data embedded into savegames. + * + * Originally written by Malte Thiesen. + */ +class PNGLoader { +protected: + PNGLoader() {} // Protected constructor to prevent instances + + static bool doDecodeImage(const byte *fileDataPtr, uint fileSize, byte *&uncompressedDataPtr, int &width, int &height, int &pitch); + static bool doImageProperties(const byte *fileDataPtr, uint fileSize, int &width, int &height); + +public: + + /** + * Decode an image. + * @param[in] fileDatePtr pointer to the image data + * @param[in] fileSize size of the image data in bytes + * @param[out] pUncompressedData if successful, this is set to a pointer containing the decoded image data + * @param[out] width if successful, this is set to the width of the image + * @param[out] height if successful, this is set to the height of the image + * @param[out] pitch if successful, this is set to the number of bytes per scanline in the image + * @return false in case of an error + * + * @remark The size of the output data equals pitch * height. + * @remark This function does not free the image buffer passed to it, + * it is the callers responsibility to do so. + */ + static bool decodeImage(const byte *pFileData, uint fileSize, + byte *&pUncompressedData, + int &width, int &height, + int &pitch); + /** + * Extract the properties of an image. + * @param[in] fileDatePtr pointer to the image data + * @param[in] fileSize size of the image data in bytes + * @param[out] width if successful, this is set to the width of the image + * @param[out] height if successful, this is set to the height of the image + * @return returns true if extraction of the properties was successful, false in case of an error + * + * @remark This function does not free the image buffer passed to it, + * it is the callers responsibility to do so. + */ + static bool imageProperties(const byte *fileDatePtr, uint fileSize, + int &width, + int &height); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/renderedimage.cpp b/engines/sword25/gfx/image/renderedimage.cpp new file mode 100644 index 0000000000..9392eca044 --- /dev/null +++ b/engines/sword25/gfx/image/renderedimage.cpp @@ -0,0 +1,397 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// ----------------------------------------------------------------------------- +// INCLUDES +// ----------------------------------------------------------------------------- + +#include "sword25/package/packagemanager.h" +#include "sword25/gfx/image/pngloader.h" +#include "sword25/gfx/image/renderedimage.h" + +#include "common/system.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "RENDEREDIMAGE" + +// ----------------------------------------------------------------------------- +// CONSTRUCTION / DESTRUCTION +// ----------------------------------------------------------------------------- + +RenderedImage::RenderedImage(const Common::String &filename, bool &result) : + _data(0), + _width(0), + _height(0) { + result = false; + + PackageManager *pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(pPackage); + + _backSurface = Kernel::getInstance()->getGfx()->getSurface(); + + // Datei laden + byte *pFileData; + uint fileSize; + if (!(pFileData = (byte *)pPackage->getFile(filename, &fileSize))) { + BS_LOG_ERRORLN("File \"%s\" could not be loaded.", filename.c_str()); + return; + } + + // Bildeigenschaften bestimmen + int pitch; + if (!PNGLoader::imageProperties(pFileData, fileSize, _width, _height)) { + BS_LOG_ERRORLN("Could not read image properties."); + delete[] pFileData; + return; + } + + // Das Bild dekomprimieren + if (!PNGLoader::decodeImage(pFileData, fileSize, _data, _width, _height, pitch)) { + BS_LOG_ERRORLN("Could not decode image."); + delete[] pFileData; + return; + } + + // Dateidaten freigeben + delete[] pFileData; + + _doCleanup = true; + + result = true; + return; +} + +// ----------------------------------------------------------------------------- + +RenderedImage::RenderedImage(uint width, uint height, bool &result) : + _width(width), + _height(height) { + + _data = new byte[width * height * 4]; + Common::set_to(_data, &_data[width * height * 4], 0); + + _backSurface = Kernel::getInstance()->getGfx()->getSurface(); + + _doCleanup = true; + + result = true; + return; +} + +RenderedImage::RenderedImage() : _width(0), _height(0), _data(0) { + _backSurface = Kernel::getInstance()->getGfx()->getSurface(); + + _doCleanup = false; + + return; +} + +// ----------------------------------------------------------------------------- + +RenderedImage::~RenderedImage() { + if (_doCleanup) + delete[] _data; +} + +// ----------------------------------------------------------------------------- + +bool RenderedImage::fill(const Common::Rect *pFillRect, uint color) { + BS_LOG_ERRORLN("Fill() is not supported."); + return false; +} + +// ----------------------------------------------------------------------------- + +bool RenderedImage::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + // Überprüfen, ob PixelData ausreichend viele Pixel enthält um ein Bild der Größe Width * Height zu erzeugen + if (size < static_cast<uint>(_width * _height * 4)) { + BS_LOG_ERRORLN("PixelData vector is too small to define a 32 bit %dx%d image.", _width, _height); + return false; + } + + const byte *in = &pixeldata[offset]; + byte *out = _data; + + for (int i = 0; i < _height; i++) { + memcpy(out, in, _width * 4); + out += _width * 4; + in += stride; + } + + return true; +} + +void RenderedImage::replaceContent(byte *pixeldata, int width, int height) { + _width = width; + _height = height; + _data = pixeldata; +} +// ----------------------------------------------------------------------------- + +uint RenderedImage::getPixel(int x, int y) { + BS_LOG_ERRORLN("GetPixel() is not supported. Returning black."); + return 0; +} + +// ----------------------------------------------------------------------------- + +bool RenderedImage::blit(int posX, int posY, int flipping, Common::Rect *pPartRect, uint color, int width, int height) { + int ca = (color >> 24) & 0xff; + + // Check if we need to draw anything at all + if (ca == 0) + return true; + + int cr = (color >> 16) & 0xff; + int cg = (color >> 8) & 0xff; + int cb = (color >> 0) & 0xff; + + // Compensate for transparency. Since we're coming + // down to 255 alpha, we just compensate for the colors here + if (ca != 255) { + cr = cr * ca >> 8; + cg = cg * ca >> 8; + cb = cb * ca >> 8; + } + + // Create an encapsulating surface for the data + Graphics::Surface srcImage; + srcImage.bytesPerPixel = 4; + srcImage.pitch = _width * 4; + srcImage.w = _width; + srcImage.h = _height; + srcImage.pixels = _data; + + if (pPartRect) { + srcImage.pixels = &_data[pPartRect->top * srcImage.pitch + pPartRect->left * 4]; + srcImage.w = pPartRect->right - pPartRect->left; + srcImage.h = pPartRect->bottom - pPartRect->top; + + debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, + pPartRect->left, pPartRect->top, pPartRect->width(), pPartRect->height(), color, width, height); + } else { + + debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, 0, 0, + srcImage.w, srcImage.h, color, width, height); + } + + if (width == -1) + width = srcImage.w; + if (height == -1) + height = srcImage.h; + +#ifdef SCALING_TESTING + // Hardcode scaling to 66% to test scaling + width = width * 2 / 3; + height = height * 2 / 3; +#endif + + Graphics::Surface *img; + Graphics::Surface *imgScaled = NULL; + byte *savedPixels = NULL; + if ((width != srcImage.w) || (height != srcImage.h)) { + // Scale the image + img = imgScaled = scale(srcImage, width, height); + savedPixels = (byte *)img->pixels; + } else { + img = &srcImage; + } + + // Handle off-screen clipping + if (posY < 0) { + img->h = MAX(0, (int)img->h - -posY); + img->pixels = (byte *)img->pixels + img->pitch * -posY; + posY = 0; + } + + if (posX < 0) { + img->w = MAX(0, (int)img->w - -posX); + img->pixels = (byte *)img->pixels + (-posX * 4); + posX = 0; + } + + img->w = CLIP((int)img->w, 0, (int)MAX((int)_backSurface->w - posX, 0)); + img->h = CLIP((int)img->h, 0, (int)MAX((int)_backSurface->h - posY, 0)); + + if ((img->w > 0) && (img->h > 0)) { + int xp = 0, yp = 0; + + int inStep = 4; + int inoStep = img->pitch; + if (flipping & Image::FLIP_V) { + inStep = -inStep; + xp = img->w - 1; + } + + if (flipping & Image::FLIP_H) { + inoStep = -inoStep; + yp = img->h - 1; + } + + byte *ino = (byte *)img->getBasePtr(xp, yp); + byte *outo = (byte *)_backSurface->getBasePtr(posX, posY); + byte *in, *out; + + for (int i = 0; i < img->h; i++) { + out = outo; + in = ino; + for (int j = 0; j < img->w; j++) { + int r = in[0]; + int g = in[1]; + int b = in[2]; + int a = in[3]; + in += inStep; + + if (ca != 255) { + a = a * ca >> 8; + } + + switch (a) { + case 0: // Full transparency + out += 4; + break; + case 255: // Full opacity + if (cr != 255) + *out++ = (r * cr) >> 8; + else + *out++ = r; + + if (cg != 255) + *out++ = (g * cg) >> 8; + else + *out++ = g; + + if (cb != 255) + *out++ = (b * cb) >> 8; + else + *out++ = b; + + *out++ = a; + break; + + default: // alpha blending + if (cr != 255) + *out += ((r - *out) * a * cr) >> 16; + else + *out += ((r - *out) * a) >> 8; + out++; + if (cg != 255) + *out += ((g - *out) * a * cg) >> 16; + else + *out += ((g - *out) * a) >> 8; + out++; + if (cb != 255) + *out += ((b - *out) * a * cb) >> 16; + else + *out += ((b - *out) * a) >> 8; + out++; + *out = 255; + out++; + } + } + outo += _backSurface->pitch; + ino += inoStep; + } + + g_system->copyRectToScreen((byte *)_backSurface->getBasePtr(posX, posY), _backSurface->pitch, posX, posY, + img->w, img->h); + } + + if (imgScaled) { + imgScaled->pixels = savedPixels; + imgScaled->free(); + delete imgScaled; + } + + return true; +} + +/** + * Scales a passed surface, creating a new surface with the result + * @param srcImage Source image to scale + * @param scaleFactor Scale amount. Must be between 0 and 1.0 (but not zero) + * @remarks Caller is responsible for freeing the returned surface + */ +Graphics::Surface *RenderedImage::scale(const Graphics::Surface &srcImage, int xSize, int ySize) { + Graphics::Surface *s = new Graphics::Surface(); + s->create(xSize, ySize, srcImage.bytesPerPixel); + + int *horizUsage = scaleLine(xSize, srcImage.w); + int *vertUsage = scaleLine(ySize, srcImage.h); + + // Loop to create scaled version + for (int yp = 0; yp < ySize; ++yp) { + const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]); + byte *destP = (byte *)s->getBasePtr(0, yp); + + for (int xp = 0; xp < xSize; ++xp) { + const byte *tempSrcP = srcP + (horizUsage[xp] * srcImage.bytesPerPixel); + for (int byteCtr = 0; byteCtr < srcImage.bytesPerPixel; ++byteCtr) { + *destP++ = *tempSrcP++; + } + } + } + + // Delete arrays and return surface + delete[] horizUsage; + delete[] vertUsage; + return s; +} + +/** + * Returns an array indicating which pixels of a source image horizontally or vertically get + * included in a scaled image + */ +int *RenderedImage::scaleLine(int size, int srcSize) { + int scale = 100 * size / srcSize; + assert(scale > 0); + int *v = new int[size]; + Common::set_to(v, &v[size], 0); + + int distCtr = 0; + int *destP = v; + for (int distIndex = 0; distIndex < srcSize; ++distIndex) { + distCtr += scale; + while (distCtr >= 100) { + assert(destP < &v[size]); + *destP++ = distIndex; + distCtr -= 100; + } + } + + return v; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/renderedimage.h b/engines/sword25/gfx/image/renderedimage.h new file mode 100644 index 0000000000..a9f2f1823c --- /dev/null +++ b/engines/sword25/gfx/image/renderedimage.h @@ -0,0 +1,121 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_RENDERED_IMAGE_H +#define SWORD25_RENDERED_IMAGE_H + +// ----------------------------------------------------------------------------- +// INCLUDES +// ----------------------------------------------------------------------------- + +#include "sword25/kernel/common.h" +#include "sword25/gfx/image/image.h" +#include "sword25/gfx/graphicengine.h" + +namespace Sword25 { + +class RenderedImage : public Image { +public: + RenderedImage(const Common::String &filename, bool &result); + + /** + @brief Erzeugt ein leeres BS_RenderedImage + + @param Width die Breite des zu erzeugenden Bildes. + @param Height die Höhe des zu erzeugenden Bildes + @param Result gibt dem Aufrufer bekannt, ob der Konstruktor erfolgreich ausgeführt wurde. Wenn es nach dem Aufruf false enthalten sollte, + dürfen keine Methoden am Objekt aufgerufen werden und das Objekt ist sofort zu zerstören. + */ + RenderedImage(uint width, uint height, bool &result); + RenderedImage(); + + virtual ~RenderedImage(); + + virtual int getWidth() const { + return _width; + } + virtual int getHeight() const { + return _height; + } + virtual GraphicEngine::COLOR_FORMATS getColorFormat() const { + return GraphicEngine::CF_ARGB32; + } + + virtual bool blit(int posX = 0, int posY = 0, + int flipping = Image::FLIP_NONE, + Common::Rect *pPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1); + virtual bool fill(const Common::Rect *pFillRect, uint color); + virtual bool setContent(const byte *pixeldata, uint size, uint offset = 0, uint stride = 0); + void replaceContent(byte *pixeldata, int width, int height); + virtual uint getPixel(int x, int y); + + virtual bool isBlitSource() const { + return true; + } + virtual bool isBlitTarget() const { + return false; + } + virtual bool isScalingAllowed() const { + return true; + } + virtual bool isFillingAllowed() const { + return false; + } + virtual bool isAlphaAllowed() const { + return true; + } + virtual bool isColorModulationAllowed() const { + return true; + } + virtual bool isSetContentAllowed() const { + return true; + } + + static Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize); +private: + byte *_data; + int _width; + int _height; + bool _doCleanup; + + Graphics::Surface *_backSurface; + + static int *scaleLine(int size, int srcSize); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/swimage.cpp b/engines/sword25/gfx/image/swimage.cpp new file mode 100644 index 0000000000..f0a8899bb6 --- /dev/null +++ b/engines/sword25/gfx/image/swimage.cpp @@ -0,0 +1,115 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/package/packagemanager.h" +#include "sword25/gfx/image/pngloader.h" +#include "sword25/gfx/image/swimage.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "SWIMAGE" + + +SWImage::SWImage(const Common::String &filename, bool &result) : + _imageDataPtr(0), + _width(0), + _height(0) { + result = false; + + PackageManager *pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(pPackage); + + // Datei laden + byte *pFileData; + uint fileSize; + if (!(pFileData = (byte *)pPackage->getFile(filename, &fileSize))) { + BS_LOG_ERRORLN("File \"%s\" could not be loaded.", filename.c_str()); + return; + } + + // Bildeigenschaften bestimmen + int pitch; + if (!PNGLoader::imageProperties(pFileData, fileSize, _width, _height)) { + BS_LOG_ERRORLN("Could not read image properties."); + return; + } + + // Das Bild dekomprimieren + byte *pUncompressedData; + if (!PNGLoader::decodeImage(pFileData, fileSize, pUncompressedData, _width, _height, pitch)) { + BS_LOG_ERRORLN("Could not decode image."); + return; + } + + // Dateidaten freigeben + delete[] pFileData; + + _imageDataPtr = (uint *)pUncompressedData; + + result = true; + return; +} + +SWImage::~SWImage() { + delete[] _imageDataPtr; +} + + +bool SWImage::blit(int posX, int posY, + int flipping, + Common::Rect *pPartRect, + uint color, + int width, int height) { + BS_LOG_ERRORLN("Blit() is not supported."); + return false; +} + +bool SWImage::fill(const Common::Rect *pFillRect, uint color) { + BS_LOG_ERRORLN("Fill() is not supported."); + return false; +} + +bool SWImage::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + BS_LOG_ERRORLN("SetContent() is not supported."); + return false; +} + +uint SWImage::getPixel(int x, int y) { + BS_ASSERT(x >= 0 && x < _width); + BS_ASSERT(y >= 0 && y < _height); + + return _imageDataPtr[_width * y + x]; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/swimage.h b/engines/sword25/gfx/image/swimage.h new file mode 100644 index 0000000000..a914c4f41f --- /dev/null +++ b/engines/sword25/gfx/image/swimage.h @@ -0,0 +1,99 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_SWIMAGE_H +#define SWORD25_SWIMAGE_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/image/image.h" +#include "sword25/gfx/graphicengine.h" + + +namespace Sword25 { + +class SWImage : public Image { +public: + SWImage(const Common::String &filename, bool &result); + virtual ~SWImage(); + + virtual int getWidth() const { + return _width; + } + virtual int getHeight() const { + return _height; + } + virtual GraphicEngine::COLOR_FORMATS getColorFormat() const { + return GraphicEngine::CF_ARGB32; + } + + virtual bool blit(int posX = 0, int posY = 0, + int flipping = Image::FLIP_NONE, + Common::Rect *pPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1); + virtual bool fill(const Common::Rect *fillRectPtr, uint color); + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride); + virtual uint getPixel(int x, int y); + + virtual bool isBlitSource() const { + return false; + } + virtual bool isBlitTarget() const { + return false; + } + virtual bool isScalingAllowed() const { + return false; + } + virtual bool isFillingAllowed() const { + return false; + } + virtual bool isAlphaAllowed() const { + return false; + } + virtual bool isColorModulationAllowed() const { + return false; + } + virtual bool isSetContentAllowed() const { + return false; + } +private: + uint *_imageDataPtr; + + int _width; + int _height; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/vectorimage.cpp b/engines/sword25/gfx/image/vectorimage.cpp new file mode 100644 index 0000000000..5c15c4771a --- /dev/null +++ b/engines/sword25/gfx/image/vectorimage.cpp @@ -0,0 +1,636 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "sword25/gfx/image/art.h" +#include "sword25/gfx/image/vectorimage.h" +#include "sword25/gfx/image/renderedimage.h" + +#include "graphics/colormasks.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "VECTORIMAGE" + +#define BEZSMOOTHNESS 0.5 + +// ----------------------------------------------------------------------------- +// SWF Datentypen +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// Bitstream Hilfsklasse +// ----------------------------------------------------------------------------- +// Das Parsen von SWF-Dateien erfordert sowohl bitweises Auslesen als auch an +// Bytegrenzen ausgerichtetes Lesen. +// Diese Klasse ist speziell dafür ausgestattet. +// ----------------------------------------------------------------------------- + +class VectorImage::SWFBitStream { +public: + SWFBitStream(const byte *pData, uint dataSize) : + m_Pos(pData), m_End(pData + dataSize), m_WordMask(0) + {} + + inline uint32 getBits(uint bitCount) { + if (bitCount == 0 || bitCount > 32) { + error("SWFBitStream::GetBits() must read at least 1 and at most 32 bits at a time"); + } + + uint32 value = 0; + while (bitCount) { + if (m_WordMask == 0) + flushByte(); + + value <<= 1; + value |= ((m_Word & m_WordMask) != 0) ? 1 : 0; + m_WordMask >>= 1; + + --bitCount; + } + + return value; + } + + inline int32 getSignedBits(uint bitCount) { + // Bits einlesen + uint32 temp = getBits(bitCount); + + // Falls das Sign-Bit gesetzt ist, den Rest des Rückgabewertes mit 1-Bits auffüllen (Sign Extension) + if (temp & 1 << (bitCount - 1)) + return (0xffffffff << bitCount) | temp; + else + return temp; + } + + inline uint32 getUInt32() { + uint32 byte1 = getByte(); + uint32 byte2 = getByte(); + uint32 byte3 = getByte(); + uint32 byte4 = getByte(); + + return byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24); + } + + inline uint16 getUInt16() { + uint32 byte1 = getByte(); + uint32 byte2 = getByte(); + + return byte1 | (byte2 << 8); + } + + inline byte getByte() { + flushByte(); + byte value = m_Word; + m_WordMask = 0; + flushByte(); + + return value; + } + + inline void flushByte() { + if (m_WordMask != 128) { + if (m_Pos >= m_End) { + error("Attempted to read past end of file"); + } else { + m_Word = *m_Pos++; + m_WordMask = 128; + } + } + } + + inline void skipBytes(uint skipLength) { + flushByte(); + if (m_Pos + skipLength >= m_End) { + error("Attempted to read past end of file"); + } else { + m_Pos += skipLength; + m_Word = *(m_Pos - 1); + } + } + +private: + const byte *m_Pos; + const byte *m_End; + + byte m_Word; + uint m_WordMask; +}; + + +// ----------------------------------------------------------------------------- +// Konstanten und Hilfsfunktionen +// ----------------------------------------------------------------------------- + +namespace { +// ----------------------------------------------------------------------------- +// Konstanten +// ----------------------------------------------------------------------------- + +const uint32 MAX_ACCEPTED_FLASH_VERSION = 3; // Die höchste Flash-Dateiversion, die vom Lader akzeptiert wird + + +// ----------------------------------------------------------------------------- +// Konvertiert SWF-Rechteckdaten in einem Bitstrom in Common::Rect-Objekte +// ----------------------------------------------------------------------------- + +Common::Rect flashRectToBSRect(VectorImage::SWFBitStream &bs) { + bs.flushByte(); + + // Feststellen mit wie vielen Bits die einzelnen Komponenten kodiert sind + uint32 bitsPerValue = bs.getBits(5); + + // Die einzelnen Komponenten einlesen + int32 xMin = bs.getSignedBits(bitsPerValue); + int32 xMax = bs.getSignedBits(bitsPerValue); + int32 yMin = bs.getSignedBits(bitsPerValue); + int32 yMax = bs.getSignedBits(bitsPerValue); + + return Common::Rect(xMin, yMin, xMax + 1, yMax + 1); +} + +// ----------------------------------------------------------------------------- +// Berechnet die Bounding-Box eines BS_VectorImageElement +// ----------------------------------------------------------------------------- + +Common::Rect CalculateBoundingBox(const VectorImageElement &vectorImageElement) { + double x0 = 0.0, y0 = 0.0, x1 = 0.0, y1 = 0.0; + + for (int j = vectorImageElement.getPathCount() - 1; j >= 0; j--) { + ArtBpath *bez = vectorImageElement.getPathInfo(j).getVec(); + ArtVpath *vec = art_bez_path_to_vec(bez, 0.5); + + if (vec[0].code == ART_END) { + continue; + } else { + x0 = x1 = vec[0].x; + y0 = y1 = vec[0].y; + for (int i = 1; vec[i].code != ART_END; i++) { + if (vec[i].x < x0) x0 = vec[i].x; + if (vec[i].x > x1) x1 = vec[i].x; + if (vec[i].y < y0) y0 = vec[i].y; + if (vec[i].y > y1) y1 = vec[i].y; + } + } + free(vec); + } + + return Common::Rect(static_cast<int>(x0), static_cast<int>(y0), static_cast<int>(x1) + 1, static_cast<int>(y1) + 1); +} + +} + + +// ----------------------------------------------------------------------------- +// Konstruktion +// ----------------------------------------------------------------------------- + +VectorImage::VectorImage(const byte *pFileData, uint fileSize, bool &success, const Common::String &fname) : _pixelData(0), _fname(fname) { + success = false; + + // Bitstream-Objekt erzeugen + // Im Folgenden werden die Dateidaten aus diesem ausgelesen. + SWFBitStream bs(pFileData, fileSize); + + // SWF-Signatur überprüfen + uint32 signature[3]; + signature[0] = bs.getByte(); + signature[1] = bs.getByte(); + signature[2] = bs.getByte(); + if (signature[0] != 'F' || + signature[1] != 'W' || + signature[2] != 'S') { + BS_LOG_ERRORLN("File is not a valid SWF-file"); + return; + } + + // Versionsangabe überprüfen + uint32 version = bs.getByte(); + if (version > MAX_ACCEPTED_FLASH_VERSION) { + BS_LOG_ERRORLN("File is of version %d. Highest accepted version is %d.", version, MAX_ACCEPTED_FLASH_VERSION); + return; + } + + // Dateigröße auslesen und mit der tatsächlichen Größe vergleichen + uint32 storedFileSize = bs.getUInt32(); + if (storedFileSize != fileSize) { + BS_LOG_ERRORLN("File is not a valid SWF-file"); + return; + } + + // SWF-Maße auslesen + Common::Rect movieRect = flashRectToBSRect(bs); + + // Framerate und Frameanzahl auslesen + /* uint32 frameRate = */ + bs.getUInt16(); + /* uint32 frameCount = */ + bs.getUInt16(); + + // Tags parsen + // Da wir uns nur für das erste DefineShape-Tag interessieren + bool keepParsing = true; + while (keepParsing) { + // Tags beginnen immer an Bytegrenzen + bs.flushByte(); + + // Tagtyp und Länge auslesen + uint16 tagTypeAndLength = bs.getUInt16(); + uint32 tagType = tagTypeAndLength >> 6; + uint32 tagLength = tagTypeAndLength & 0x3f; + if (tagLength == 0x3f) + tagLength = bs.getUInt32(); + + switch (tagType) { + case 2: + // DefineShape + success = parseDefineShape(2, bs); + return; + case 22: + // DefineShape2 + success = parseDefineShape(2, bs); + return; + case 32: + success = parseDefineShape(3, bs); + return; + default: + // Unbekannte Tags ignorieren + bs.skipBytes(tagLength); + } + } + + // Die Ausführung darf nicht an dieser Stelle ankommen: Entweder es wird ein Shape gefunden, dann wird die Funktion mit vorher verlassen, oder + // es wird keines gefunden, dann tritt eine Exception auf sobald über das Ende der Datei hinaus gelesen wird. + BS_ASSERT(false); +} + +VectorImage::~VectorImage() { + for (int j = _elements.size() - 1; j >= 0; j--) + for (int i = _elements[j].getPathCount() - 1; i >= 0; i--) + if (_elements[j].getPathInfo(i).getVec()) + free(_elements[j].getPathInfo(i).getVec()); + + if (_pixelData) + free(_pixelData); +} + + +ArtBpath *ensureBezStorage(ArtBpath *bez, int nodes, int *allocated) { + if (*allocated <= nodes) { + (*allocated) += 20; + + return art_renew(bez, ArtBpath, *allocated); + } + + return bez; +} + +ArtBpath *VectorImage::storeBez(ArtBpath *bez, int lineStyle, int fillStyle0, int fillStyle1, int *bezNodes, int *bezAllocated) { + (*bezNodes)++; + + bez = ensureBezStorage(bez, *bezNodes, bezAllocated); + bez[*bezNodes].code = ART_END; + + ArtBpath *bez1 = art_new(ArtBpath, *bezNodes + 1); + + for (int i = 0; i <= *bezNodes; i++) + bez1[i] = bez[i]; + + _elements.back()._pathInfos.push_back(VectorPathInfo(bez1, *bezNodes, lineStyle, fillStyle0, fillStyle1)); + + return bez; +} + +bool VectorImage::parseDefineShape(uint shapeType, SWFBitStream &bs) { + /*uint32 shapeID = */bs.getUInt16(); + + // Bounding Box auslesen + _boundingBox = flashRectToBSRect(bs); + + // Erstes Image-Element erzeugen + _elements.resize(1); + + // Styles einlesen + uint numFillBits; + uint numLineBits; + if (!parseStyles(shapeType, bs, numFillBits, numLineBits)) + return false; + + uint lineStyle = 0; + uint fillStyle0 = 0; + uint fillStyle1 = 0; + + // Shaperecord parsen + // ------------------ + + double curX = 0; + double curY = 0; + int bezNodes = 0; + int bezAllocated = 10; + ArtBpath *bez = art_new(ArtBpath, bezAllocated); + + bool endOfShapeDiscovered = false; + while (!endOfShapeDiscovered) { + uint32 typeFlag = bs.getBits(1); + + // Non-Edge Record + if (typeFlag == 0) { + // Feststellen welche Parameter gesetzt werden + uint32 stateNewStyles = bs.getBits(1); + uint32 stateLineStyle = bs.getBits(1); + uint32 stateFillStyle1 = bs.getBits(1); + uint32 stateFillStyle0 = bs.getBits(1); + uint32 stateMoveTo = bs.getBits(1); + + uint prevLineStyle = lineStyle; + uint prevFillStyle0 = fillStyle0; + uint prevFillStyle1 = fillStyle1; + + // End der Shape-Definition erreicht? + if (!stateNewStyles && !stateLineStyle && !stateFillStyle0 && !stateFillStyle1 && !stateMoveTo) { + endOfShapeDiscovered = true; + // Parameter dekodieren + } else { + if (stateMoveTo) { + uint32 moveToBits = bs.getBits(5); + curX = bs.getSignedBits(moveToBits); + curY = bs.getSignedBits(moveToBits); + } + + if (stateFillStyle0) { + if (numFillBits > 0) + fillStyle0 = bs.getBits(numFillBits); + else + fillStyle0 = 0; + } + + if (stateFillStyle1) { + if (numFillBits > 0) + fillStyle1 = bs.getBits(numFillBits); + else + fillStyle1 = 0; + } + + if (stateLineStyle) { + if (numLineBits) + lineStyle = bs.getBits(numLineBits); + else + numLineBits = 0; + } + + // Ein neuen Pfad erzeugen, es sei denn, es wurden nur neue Styles definiert + if (stateLineStyle || stateFillStyle0 || stateFillStyle1 || stateMoveTo) { + // Store previous curve if any + if (bezNodes) { + bez = storeBez(bez, prevLineStyle, prevFillStyle0, prevFillStyle1, &bezNodes, &bezAllocated); + } + + // Start new curve + bez = ensureBezStorage(bez, 1, &bezAllocated); + bez[0].code = ART_MOVETO_OPEN; + bez[0].x3 = curX; + bez[0].y3 = curY; + bezNodes = 0; + } + + if (stateNewStyles) { + // An dieser Stelle werden in Flash die alten Style-Definitionen verworfen und mit den neuen überschrieben. + // Es wird ein neues Element begonnen. + _elements.resize(_elements.size() + 1); + if (!parseStyles(shapeType, bs, numFillBits, numLineBits)) + return false; + } + } + } else { + // Edge Record + uint32 edgeFlag = bs.getBits(1); + uint32 numBits = bs.getBits(4) + 2; + + // Curved edge + if (edgeFlag == 0) { + double controlDeltaX = bs.getSignedBits(numBits); + double controlDeltaY = bs.getSignedBits(numBits); + double anchorDeltaX = bs.getSignedBits(numBits); + double anchorDeltaY = bs.getSignedBits(numBits); + + double controlX = curX + controlDeltaX; + double controlY = curY + controlDeltaY; + double newX = controlX + anchorDeltaX; + double newY = controlY + anchorDeltaY; + +#define WEIGHT (2.0/3.0) + + bezNodes++; + bez = ensureBezStorage(bez, bezNodes, &bezAllocated); + bez[bezNodes].code = ART_CURVETO; + bez[bezNodes].x1 = WEIGHT * controlX + (1 - WEIGHT) * curX; + bez[bezNodes].y1 = WEIGHT * controlY + (1 - WEIGHT) * curY; + bez[bezNodes].x2 = WEIGHT * controlX + (1 - WEIGHT) * newX; + bez[bezNodes].y2 = WEIGHT * controlY + (1 - WEIGHT) * newY; + bez[bezNodes].x3 = newX; + bez[bezNodes].y3 = newY; + + curX = newX; + curY = newY; + } else { + // Staight edge + int32 deltaX = 0; + int32 deltaY = 0; + + uint32 generalLineFlag = bs.getBits(1); + if (generalLineFlag) { + deltaX = bs.getSignedBits(numBits); + deltaY = bs.getSignedBits(numBits); + } else { + uint32 vertLineFlag = bs.getBits(1); + if (vertLineFlag) + deltaY = bs.getSignedBits(numBits); + else + deltaX = bs.getSignedBits(numBits); + } + + curX += deltaX; + curY += deltaY; + + bezNodes++; + bez = ensureBezStorage(bez, bezNodes, &bezAllocated); + bez[bezNodes].code = ART_LINETO; + bez[bezNodes].x3 = curX; + bez[bezNodes].y3 = curY; + } + } + } + + // Store last curve + if (bezNodes) + bez = storeBez(bez, lineStyle, fillStyle0, fillStyle1, &bezNodes, &bezAllocated); + + free(bez); + + // Bounding-Boxes der einzelnen Elemente berechnen + Common::Array<VectorImageElement>::iterator it = _elements.begin(); + for (; it != _elements.end(); ++it) + it->_boundingBox = CalculateBoundingBox(*it); + + return true; +} + + +// ----------------------------------------------------------------------------- + +bool VectorImage::parseStyles(uint shapeType, SWFBitStream &bs, uint &numFillBits, uint &numLineBits) { + bs.flushByte(); + + // Fillstyles parsen + // ----------------- + + // Anzahl an Fillstyles bestimmen + uint fillStyleCount = bs.getByte(); + if (fillStyleCount == 0xff) + fillStyleCount = bs.getUInt16(); + + // Alle Fillstyles einlesen, falls ein Fillstyle mit Typ != 0 gefunden wird, wird das Parsen abgebrochen. + // Es wird nur "solid fill" (Typ 0) unterstützt. + _elements.back()._fillStyles.reserve(fillStyleCount); + for (uint i = 0; i < fillStyleCount; ++i) { + byte type = bs.getByte(); + uint32 color; + byte r = bs.getByte(); + byte g = bs.getByte(); + byte b = bs.getByte(); + byte a = 0xff; + + if (shapeType == 3) + a = bs.getByte(); + + color = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(a, r, g, b); + + if (type != 0) + return false; + + _elements.back()._fillStyles.push_back(color); + } + + // Linestyles parsen + // ----------------- + + // Anzahl an Linestyles bestimmen + uint lineStyleCount = bs.getByte(); + if (lineStyleCount == 0xff) + lineStyleCount = bs.getUInt16(); + + // Alle Linestyles einlesen + _elements.back()._lineStyles.reserve(lineStyleCount); + for (uint i = 0; i < lineStyleCount; ++i) { + double width = bs.getUInt16(); + uint32 color; + byte r = bs.getByte(); + byte g = bs.getByte(); + byte b = bs.getByte(); + byte a = 0xff; + + if (shapeType == 3) + a = bs.getByte(); + + color = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(a, r, g, b); + + _elements.back()._lineStyles.push_back(VectorImageElement::LineStyleType(width, color)); + } + + // Bitbreite für die folgenden Styleindizes auslesen + numFillBits = bs.getBits(4); + numLineBits = bs.getBits(4); + + return true; +} + + +// ----------------------------------------------------------------------------- + +bool VectorImage::fill(const Common::Rect *pFillRect, uint color) { + BS_LOG_ERRORLN("Fill() is not supported."); + return false; +} + + +// ----------------------------------------------------------------------------- + +uint VectorImage::getPixel(int x, int y) { + BS_LOG_ERRORLN("GetPixel() is not supported. Returning black."); + return 0; +} + +// ----------------------------------------------------------------------------- + +bool VectorImage::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + BS_LOG_ERRORLN("SetContent() is not supported."); + return 0; +} + +bool VectorImage::blit(int posX, int posY, + int flipping, + Common::Rect *pPartRect, + uint color, + int width, int height) { + static VectorImage *oldThis = 0; + static int oldWidth = -2; + static int oldHeight = -2; + + // Falls Breite oder Höhe 0 sind, muss nichts dargestellt werden. + if (width == 0 || height == 0) + return true; + + // Feststellen, ob das alte Bild im Cache nicht wiederbenutzt werden kann und neu Berechnet werden muss + if (!(oldThis == this && oldWidth == width && oldHeight == height)) { + render(width, height); + + oldThis = this; + oldHeight = height; + oldWidth = width; + } + + RenderedImage *rend = new RenderedImage(); + + rend->replaceContent(_pixelData, width, height); + rend->blit(posX, posY, flipping, pPartRect, color, width, height); + + delete rend; + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/image/vectorimage.h b/engines/sword25/gfx/image/vectorimage.h new file mode 100644 index 0000000000..3477463b43 --- /dev/null +++ b/engines/sword25/gfx/image/vectorimage.h @@ -0,0 +1,237 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_VECTORIMAGE_H +#define SWORD25_VECTORIMAGE_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "sword25/kernel/common.h" +#include "sword25/gfx/image/image.h" +#include "common/rect.h" + +#include "art.h" + +namespace Sword25 { + +class VectorImage; + +/** + @brief Pfadinformationen zu BS_VectorImageElement Objekten + + Jedes BS_VectorImageElement besteht aus Kantenzügen, oder auch Pfaden. Jeder dieser Pfad hat Eigenschaften, die in Objekten diesen Typs + gespeichert werden. +*/ + +class VectorPathInfo { +public: + VectorPathInfo(ArtBpath *vec, int len, uint lineStyle, uint fillStyle0, uint fillStyle1) : + _vec(vec), _lineStyle(lineStyle), _fillStyle0(fillStyle0), _fillStyle1(fillStyle1), _len(len) {} + + VectorPathInfo() { + _lineStyle = _fillStyle0 = _fillStyle1 = _len = 0; + _vec = 0; + } + + ArtBpath *getVec() const { + return _vec; + } + int getVecLen() const { + return _len; + } + uint getLineStyle() const { + return _lineStyle; + } + uint getFillStyle0() const { + return _fillStyle0; + } + uint getFillStyle1() const { + return _fillStyle1; + } + +private: + ArtBpath *_vec; + uint _lineStyle; + uint _fillStyle0; + uint _fillStyle1; + uint _len; +}; + +/** + @brief Ein Element eines Vektorbild. Ein BS_VectorImage besteht aus diesen Elementen, die jeweils einen Teil der Graphik definieren. + Werden alle Elemente eines Vektorbildes übereinandergelegt, ergibt sich das komplette Bild. +*/ +class VectorImageElement { + friend class VectorImage; +public: + uint getPathCount() const { + return _pathInfos.size(); + } + const VectorPathInfo &getPathInfo(uint pathNr) const { + BS_ASSERT(pathNr < getPathCount()); + return _pathInfos[pathNr]; + } + + double getLineStyleWidth(uint lineStyle) const { + BS_ASSERT(lineStyle < _lineStyles.size()); + return _lineStyles[lineStyle].width; + } + + uint getLineStyleCount() const { + return _lineStyles.size(); + } + + uint32 getLineStyleColor(uint lineStyle) const { + BS_ASSERT(lineStyle < _lineStyles.size()); + return _lineStyles[lineStyle].color; + } + + uint getFillStyleCount() const { + return _fillStyles.size(); + } + + uint32 getFillStyleColor(uint fillStyle) const { + BS_ASSERT(fillStyle < _fillStyles.size()); + return _fillStyles[fillStyle]; + } + + const Common::Rect &getBoundingBox() const { + return _boundingBox; + } + +private: + struct LineStyleType { + LineStyleType(double width_, uint32 color_) : width(width_), color(color_) {} + LineStyleType() { + width = 0; + color = 0; + } + double width; + uint32 color; + }; + + Common::Array<VectorPathInfo> _pathInfos; + Common::Array<LineStyleType> _lineStyles; + Common::Array<uint32> _fillStyles; + Common::Rect _boundingBox; +}; + + +/** + @brief Eine Vektorgraphik + + Objekte dieser Klasse enthalten die Informationen eines SWF-Shapes. +*/ + +class VectorImage : public Image { +public: + VectorImage(const byte *pFileData, uint fileSize, bool &success, const Common::String &fname); + ~VectorImage(); + + uint getElementCount() const { + return _elements.size(); + } + const VectorImageElement &getElement(uint elementNr) const { + BS_ASSERT(elementNr < _elements.size()); + return _elements[elementNr]; + } + const Common::Rect &getBoundingBox() const { + return _boundingBox; + } + + // + // Die abstrakten Methoden von BS_Image + // + virtual int getWidth() const { + return _boundingBox.width(); + } + virtual int getHeight() const { + return _boundingBox.height(); + } + virtual GraphicEngine::COLOR_FORMATS getColorFormat() const { + return GraphicEngine::CF_ARGB32; + } + virtual bool fill(const Common::Rect *pFillRect = 0, uint color = BS_RGB(0, 0, 0)); + + void render(int width, int height); + + virtual uint getPixel(int x, int y); + virtual bool isBlitSource() const { + return true; + } + virtual bool isBlitTarget() const { + return false; + } + virtual bool isScalingAllowed() const { + return true; + } + virtual bool isFillingAllowed() const { + return false; + } + virtual bool isAlphaAllowed() const { + return true; + } + virtual bool isColorModulationAllowed() const { + return true; + } + virtual bool isSetContentAllowed() const { + return false; + } + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride); + virtual bool blit(int posX = 0, int posY = 0, + int flipping = FLIP_NONE, + Common::Rect *pPartRect = NULL, + uint color = BS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1); + + class SWFBitStream; + +private: + bool parseDefineShape(uint shapeType, SWFBitStream &bs); + bool parseStyles(uint shapeType, SWFBitStream &bs, uint &numFillBits, uint &numLineBits); + + ArtBpath *storeBez(ArtBpath *bez, int lineStyle, int fillStyle0, int fillStyle1, int *bezNodes, int *bezAllocated); + Common::Array<VectorImageElement> _elements; + Common::Rect _boundingBox; + + byte *_pixelData; + + Common::String _fname; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/image/vectorimagerenderer.cpp b/engines/sword25/gfx/image/vectorimagerenderer.cpp new file mode 100644 index 0000000000..43ac8683ac --- /dev/null +++ b/engines/sword25/gfx/image/vectorimagerenderer.cpp @@ -0,0 +1,461 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code contains portions of Libart_LGPL - library of basic graphic primitives + * + * Copyright (c) 1998 Raph Levien + * + * Licensed under GNU LGPL v2 + * + */ + +/* + * This code contains portions of Swfdec + * + * Copyright (c) 2004-2006 David Schleef <ds@schleef.org> + * + * Licensed under GNU GPL v2 + * + */ + +#include "art.h" + +#include "sword25/gfx/image/vectorimage.h" +#include "graphics/colormasks.h" + +namespace Sword25 { + +void art_rgb_fill_run1(art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n) { + int i; + + if (r == g && g == b && r == 255) { + memset(buf, g, n + n + n + n); + } else { + uint32 *alt = (uint32 *)buf; + uint32 color = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(0xff, r, g, b); + + for (i = 0; i < n; i++) + *alt++ = color; + } +} + +void art_rgb_run_alpha1(art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n) { + int i; + int v; + + for (i = 0; i < n; i++) { + v = *buf; + *buf++ = v + (((b - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((g - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((r - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = MIN(v + alpha, 0xff); + } +} + +typedef struct _ArtRgbSVPAlphaData ArtRgbSVPAlphaData; + +struct _ArtRgbSVPAlphaData { + int alphatab[256]; + art_u8 r, g, b, alpha; + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +static void art_rgb_svp_alpha_callback1(void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) { + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) { + run_x1 = steps[0].x; + if (run_x1 > x0) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], run_x1 - x0); + } + + for (k = 0; k < n_steps - 1; k++) { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf + (run_x0 - x0) * 4, r, g, b, alphatab[alpha], run_x1 - run_x0); + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf + (run_x1 - x0) * 4, r, g, b, alphatab[alpha], x1 - run_x1); + } + } else { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], x1 - x0); + } + + data->buf += data->rowstride; +} + +static void art_rgb_svp_alpha_opaque_callback1(void *callback_data, int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps) { + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) { + run_x1 = steps[0].x; + if (run_x1 > x0) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf, r, g, b, run_x1 - x0); + else + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], run_x1 - x0); + } + } + + for (k = 0; k < n_steps - 1; k++) { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf + (run_x0 - x0) * 4, r, g, b, run_x1 - run_x0); + else + art_rgb_run_alpha1(linebuf + (run_x0 - x0) * 4, r, g, b, alphatab[alpha], run_x1 - run_x0); + } + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf + (run_x1 - x0) * 4, r, g, b, x1 - run_x1); + else + art_rgb_run_alpha1(linebuf + (run_x1 - x0) * 4, r, g, b, alphatab[alpha], x1 - run_x1); + } + } + } else { + alpha = running_sum >> 16; + if (alpha) { + if (alpha >= 255) + art_rgb_fill_run1(linebuf, r, g, b, x1 - x0); + else + art_rgb_run_alpha1(linebuf, r, g, b, alphatab[alpha], x1 - x0); + } + } + + data->buf += data->rowstride; +} + +void art_rgb_svp_alpha1(const ArtSVP *svp, + int x0, int y0, int x1, int y1, + uint32 color, + art_u8 *buf, int rowstride) { + ArtRgbSVPAlphaData data; + byte r, g, b, alpha; + int i; + int a, da; + + Graphics::colorToARGB<Graphics::ColorMasks<8888> >(color, alpha, r, g, b); + + data.r = r; + data.g = g; + data.b = b; + data.alpha = alpha; + + a = 0x8000; + da = (alpha * 66051 + 0x80) >> 8; /* 66051 equals 2 ^ 32 / (255 * 255) */ + + for (i = 0; i < 256; i++) { + data.alphatab[i] = a >> 16; + a += da; + } + + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + if (alpha == 255) + art_svp_render_aa(svp, x0, y0, x1, y1, art_rgb_svp_alpha_opaque_callback1, &data); + else + art_svp_render_aa(svp, x0, y0, x1, y1, art_rgb_svp_alpha_callback1, &data); +} + +static int art_vpath_len(ArtVpath *a) { + int i = 0; + while (a[i].code != ART_END) + i++; + return i; +} + +ArtVpath *art_vpath_cat(ArtVpath *a, ArtVpath *b) { + ArtVpath *dest; + ArtVpath *p; + int len_a, len_b; + + len_a = art_vpath_len(a); + len_b = art_vpath_len(b); + dest = art_new(ArtVpath, len_a + len_b + 1); + p = dest; + + for (int i = 0; i < len_a; i++) + *p++ = *a++; + for (int i = 0; i <= len_b; i++) + *p++ = *b++; + + return dest; +} + +void art_svp_make_convex(ArtSVP *svp) { + int i; + + if (svp->n_segs > 0 && svp->segs[0].dir == 0) { + for (i = 0; i < svp->n_segs; i++) { + svp->segs[i].dir = !svp->segs[i].dir; + } + } +} + +ArtVpath *art_vpath_reverse(ArtVpath *a) { + ArtVpath *dest; + ArtVpath it; + int len; + int state = 0; + int i; + + len = art_vpath_len(a); + dest = art_new(ArtVpath, len + 1); + + for (i = 0; i < len; i++) { + it = a[len - i - 1]; + if (state) { + it.code = ART_LINETO; + } else { + it.code = ART_MOVETO_OPEN; + state = 1; + } + if (a[len - i - 1].code == ART_MOVETO || + a[len - i - 1].code == ART_MOVETO_OPEN) { + state = 0; + } + dest[i] = it; + } + dest[len] = a[len]; + + return dest; +} + +ArtVpath *art_vpath_reverse_free(ArtVpath *a) { + ArtVpath *dest; + + dest = art_vpath_reverse(a); + free(a); + + return dest; +} + +void drawBez(ArtBpath *bez1, ArtBpath *bez2, art_u8 *buffer, int width, int height, int deltaX, int deltaY, double scaleX, double scaleY, double penWidth, unsigned int color) { + ArtVpath *vec = NULL; + ArtVpath *vec1 = NULL; + ArtVpath *vec2 = NULL; + ArtSVP *svp = NULL; + +#if 0 + const char *codes[] = {"ART_MOVETO", "ART_MOVETO_OPEN", "ART_CURVETO", "ART_LINETO", "ART_END"}; + for (int i = 0;; i++) { + printf(" bez[%d].code = %s;\n", i, codes[bez[i].code]); + if (bez[i].code == ART_END) + break; + if (bez[i].code == ART_CURVETO) { + printf(" bez[%d].x1 = %f; bez[%d].y1 = %f;\n", i, bez[i].x1, i, bez[i].y1); + printf(" bez[%d].x2 = %f; bez[%d].y2 = %f;\n", i, bez[i].x2, i, bez[i].y2); + } + printf(" bez[%d].x3 = %f; bez[%d].y3 = %f;\n", i, bez[i].x3, i, bez[i].y3); + } + + printf(" drawBez(bez, buffer, 1.0, 1.0, %f, 0x%08x);\n", penWidth, color); +#endif + + // HACK: Some frames have green bounding boxes drawn. + // Perhaps they were used by original game artist Umriss + // We skip them just like the original + if (bez2 == 0 && color == Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(0xff, 0x00, 0xff, 0x00)) { + return; + } + + vec1 = art_bez_path_to_vec(bez1, 0.5); + if (bez2 != 0) { + vec2 = art_bez_path_to_vec(bez2, 0.5); + vec2 = art_vpath_reverse_free(vec2); + vec = art_vpath_cat(vec1, vec2); + + free(vec1); + free(vec2); + } else { + vec = vec1; + } + + int size = art_vpath_len(vec); + ArtVpath *vect = art_new(ArtVpath, size + 1); + + int k; + for (k = 0; k < size; k++) { + vect[k].code = vec[k].code; + vect[k].x = (vec[k].x - deltaX) * scaleX; + vect[k].y = (vec[k].y - deltaY) * scaleY; + } + vect[k].code = ART_END; + + if (bez2 == 0) { // Line drawing + svp = art_svp_vpath_stroke(vect, ART_PATH_STROKE_JOIN_ROUND, ART_PATH_STROKE_CAP_ROUND, penWidth, 1.0, 0.5); + } else { + svp = art_svp_from_vpath(vect); + art_svp_make_convex(svp); + } + + art_rgb_svp_alpha1(svp, 0, 0, width, height, color, buffer, width * 4); + + free(vect); + art_svp_free(svp); + free(vec); +} + +void VectorImage::render(int width, int height) { + double scaleX = (width == - 1) ? 1 : static_cast<double>(width) / static_cast<double>(getWidth()); + double scaleY = (height == - 1) ? 1 : static_cast<double>(height) / static_cast<double>(getHeight()); + + debug(3, "VectorImage::render(%d, %d) %s", width, height, _fname.c_str()); + + if (_pixelData) + free(_pixelData); + + _pixelData = (byte *)malloc(width * height * 4); + memset(_pixelData, 0, width * height * 4); + + for (uint e = 0; e < _elements.size(); e++) { + + //// Draw shapes + for (uint s = 0; s < _elements[e].getFillStyleCount(); s++) { + int fill0len = 0; + int fill1len = 0; + + // Count vector sizes in order to minimize memory + // fragmentation + for (uint p = 0; p < _elements[e].getPathCount(); p++) { + if (_elements[e].getPathInfo(p).getFillStyle0() == s + 1) + fill0len += _elements[e].getPathInfo(p).getVecLen(); + + if (_elements[e].getPathInfo(p).getFillStyle1() == s + 1) + fill1len += _elements[e].getPathInfo(p).getVecLen(); + } + + // Now lump together vectors + ArtBpath *fill1 = art_new(ArtBpath, fill1len + 1); + ArtBpath *fill0 = art_new(ArtBpath, fill0len + 1); + ArtBpath *fill1pos = fill1; + ArtBpath *fill0pos = fill0; + + for (uint p = 0; p < _elements[e].getPathCount(); p++) { + if (_elements[e].getPathInfo(p).getFillStyle0() == s + 1) { + for (int i = 0; i < _elements[e].getPathInfo(p).getVecLen(); i++) + *fill0pos++ = _elements[e].getPathInfo(p).getVec()[i]; + } + + if (_elements[e].getPathInfo(p).getFillStyle1() == s + 1) { + for (int i = 0; i < _elements[e].getPathInfo(p).getVecLen(); i++) + *fill1pos++ = _elements[e].getPathInfo(p).getVec()[i]; + } + } + + // Close vectors + (*fill0pos).code = ART_END; + (*fill1pos).code = ART_END; + + drawBez(fill1, fill0, _pixelData, width, height, _boundingBox.left, _boundingBox.top, scaleX, scaleY, -1, _elements[e].getFillStyleColor(s)); + + free(fill0); + free(fill1); + } + + //// Draw strokes + for (uint s = 0; s < _elements[e].getLineStyleCount(); s++) { + double penWidth = _elements[e].getLineStyleWidth(s); + penWidth *= sqrt(fabs(scaleX * scaleY)); + + for (uint p = 0; p < _elements[e].getPathCount(); p++) { + if (_elements[e].getPathInfo(p).getLineStyle() == s + 1) { + drawBez(_elements[e].getPathInfo(p).getVec(), 0, _pixelData, width, height, _boundingBox.left, _boundingBox.top, scaleX, scaleY, penWidth, _elements[e].getLineStyleColor(s)); + } + } + } + } +} + + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/panel.cpp b/engines/sword25/gfx/panel.cpp new file mode 100644 index 0000000000..2de1de133a --- /dev/null +++ b/engines/sword25/gfx/panel.cpp @@ -0,0 +1,111 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/panel.h" + +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/gfx/image/image.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "PANEL" + +Panel::Panel(RenderObjectPtr<RenderObject> parentPtr, int width, int height, uint color) : + RenderObject(parentPtr, RenderObject::TYPE_PANEL), + _color(color) { + _initSuccess = false; + + _width = width; + _height = height; + + if (_width < 0) { + BS_LOG_ERRORLN("Tried to initialise a panel with an invalid width (%d).", _width); + return; + } + + if (_height < 0) { + BS_LOG_ERRORLN("Tried to initialise a panel with an invalid height (%d).", _height); + return; + } + + _initSuccess = true; +} + +Panel::Panel(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + RenderObject(parentPtr, RenderObject::TYPE_PANEL, handle) { + _initSuccess = unpersist(reader); +} + +Panel::~Panel() { +} + +bool Panel::doRender() { + // Falls der Alphawert 0 ist, ist das Panel komplett durchsichtig und es muss nichts gezeichnet werden. + if (_color >> 24 == 0) + return true; + + GraphicEngine *gfxPtr = Kernel::getInstance()->getGfx(); + BS_ASSERT(gfxPtr); + + return gfxPtr->fill(&_bbox, _color); +} + +bool Panel::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= RenderObject::persist(writer); + writer.write(_color); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +bool Panel::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= RenderObject::unpersist(reader); + + uint color; + reader.read(color); + setColor(color); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/panel.h b/engines/sword25/gfx/panel.h new file mode 100644 index 0000000000..6fe96369a6 --- /dev/null +++ b/engines/sword25/gfx/panel.h @@ -0,0 +1,73 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_PANEL_H +#define SWORD25_PANEL_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +class Panel : public RenderObject { + friend class RenderObject; + +private: + Panel(RenderObjectPtr<RenderObject> parentPtr, int width, int height, uint color); + Panel(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + +public: + virtual ~Panel(); + + uint getColor() const { + return _color; + } + void setColor(uint color) { + _color = color; + forceRefresh(); + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + virtual bool doRender(); + +private: + uint _color; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/renderobject.cpp b/engines/sword25/gfx/renderobject.cpp new file mode 100644 index 0000000000..77af0bee92 --- /dev/null +++ b/engines/sword25/gfx/renderobject.cpp @@ -0,0 +1,517 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/renderobject.h" + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +#include "sword25/gfx/renderobjectregistry.h" +#include "sword25/gfx/renderobjectmanager.h" +#include "sword25/gfx/graphicengine.h" + +#include "sword25/gfx/bitmap.h" +#include "sword25/gfx/staticbitmap.h" +#include "sword25/gfx/dynamicbitmap.h" +#include "sword25/gfx/animation.h" +#include "sword25/gfx/panel.h" +#include "sword25/gfx/text.h" +#include "sword25/gfx/animationtemplate.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "RENDEROBJECT" + +RenderObject::RenderObject(RenderObjectPtr<RenderObject> parentPtr, TYPES type, uint handle) : + _managerPtr(0), + _parentPtr(parentPtr), + _x(0), + _y(0), + _z(0), + _oldX(-1), + _oldY(-1), + _oldZ(-1), + _width(0), + _height(0), + _visible(true), + _oldVisible(false), + _childChanged(true), + _type(type), + _initSuccess(false), + _refreshForced(true), + _handle(0) { + + // Renderobject registrieren, abhängig vom Handle-Parameter entweder mit beliebigem oder vorgegebenen Handle. + if (handle == 0) + _handle = RenderObjectRegistry::instance().registerObject(this); + else + _handle = RenderObjectRegistry::instance().registerObject(this, handle); + + if (_handle == 0) + return; + + updateAbsolutePos(); + + // Dieses Objekt zu den Kindern der Elternobjektes hinzufügen, falls nicht Wurzel (ParentPtr ungültig) und dem + // selben RenderObjektManager zuweisen. + if (_parentPtr.isValid()) { + _managerPtr = _parentPtr->getManager(); + _parentPtr->addObject(this->getHandle()); + } else { + if (getType() != TYPE_ROOT) { + BS_LOG_ERRORLN("Tried to create a non-root render object and has no parent. All non-root render objects have to have a parent."); + return; + } + } + + updateObjectState(); + + _initSuccess = true; +} + +RenderObject::~RenderObject() { + // Objekt aus dem Elternobjekt entfernen. + if (_parentPtr.isValid()) + _parentPtr->detatchChildren(this->getHandle()); + + deleteAllChildren(); + + // Objekt deregistrieren. + RenderObjectRegistry::instance().deregisterObject(this); +} + +bool RenderObject::render() { + // Objektänderungen validieren + validateObject(); + + // Falls das Objekt nicht sichtbar ist, muss gar nichts gezeichnet werden + if (!_visible) + return true; + + // Falls notwendig, wird die Renderreihenfolge der Kinderobjekte aktualisiert. + if (_childChanged) { + sortRenderObjects(); + _childChanged = false; + } + + // Objekt zeichnen. + doRender(); + + // Dann müssen die Kinder gezeichnet werden + RENDEROBJECT_ITER it = _children.begin(); + for (; it != _children.end(); ++it) + if (!(*it)->render()) + return false; + + return true; +} + +void RenderObject::validateObject() { + // Die Veränderungen in den Objektvariablen aufheben + _oldBbox = _bbox; + _oldVisible = _visible; + _oldX = _x; + _oldY = _y; + _oldZ = _z; + _refreshForced = false; +} + +bool RenderObject::updateObjectState() { + // Falls sich das Objekt verändert hat, muss der interne Zustand neu berechnet werden und evtl. Update-Regions für den nächsten Frame + // registriert werden. + if ((calcBoundingBox() != _oldBbox) || + (_visible != _oldVisible) || + (_x != _oldX) || + (_y != _oldY) || + (_z != _oldZ) || + _refreshForced) { + // Renderrang des Objektes neu bestimmen, da sich dieser verändert haben könnte + if (_parentPtr.isValid()) + _parentPtr->signalChildChange(); + + // Die Bounding-Box neu berechnen und Update-Regions registrieren. + updateBoxes(); + + // Änderungen Validieren + validateObject(); + } + + // Dann muss der Objektstatus der Kinder aktualisiert werden. + RENDEROBJECT_ITER it = _children.begin(); + for (; it != _children.end(); ++it) + if (!(*it)->updateObjectState()) + return false; + + return true; +} + +void RenderObject::updateBoxes() { + // Bounding-Box aktualisieren + _bbox = calcBoundingBox(); +} + +Common::Rect RenderObject::calcBoundingBox() const { + // Die Bounding-Box mit der Objektgröße initialisieren. + Common::Rect bbox(0, 0, _width, _height); + + // Die absolute Position der Bounding-Box berechnen. + bbox.translate(_absoluteX, _absoluteY); + + // Die Bounding-Box am Elternobjekt clippen. + if (_parentPtr.isValid()) { + bbox.clip(_parentPtr->getBbox()); + } + + return bbox; +} + +void RenderObject::calcAbsolutePos(int &x, int &y) const { + x = calcAbsoluteX(); + y = calcAbsoluteY(); +} + +int RenderObject::calcAbsoluteX() const { + if (_parentPtr.isValid()) + return _parentPtr->getAbsoluteX() + _x; + else + return _x; +} + +int RenderObject::calcAbsoluteY() const { + if (_parentPtr.isValid()) + return _parentPtr->getAbsoluteY() + _y; + else + return _y; +} + +void RenderObject::deleteAllChildren() { + // Es ist nicht notwendig die Liste zu iterieren, da jedes Kind für sich DetatchChildren an diesem Objekt aufruft und sich somit + // selber entfernt. Daher muss immer nur ein beliebiges Element (hier das letzte) gelöscht werden, bis die Liste leer ist. + while (!_children.empty()) { + RenderObjectPtr<RenderObject> curPtr = _children.back(); + curPtr.erase(); + } +} + +bool RenderObject::addObject(RenderObjectPtr<RenderObject> pObject) { + if (!pObject.isValid()) { + BS_LOG_ERRORLN("Tried to add a null object to a renderobject."); + return false; + } + + // Objekt in die Kinderliste einfügen. + _children.push_back(pObject); + + // Sicherstellen, dass vor dem nächsten Rendern die Renderreihenfolge aktualisiert wird. + if (_parentPtr.isValid()) + _parentPtr->signalChildChange(); + + return true; +} + +bool RenderObject::detatchChildren(RenderObjectPtr<RenderObject> pObject) { + // Kinderliste durchgehen und Objekt entfernen falls vorhanden + RENDEROBJECT_ITER it = _children.begin(); + for (; it != _children.end(); ++it) + if (*it == pObject) { + _children.erase(it); + return true; + } + + BS_LOG_ERRORLN("Tried to detach children from a render object that isn't its parent."); + return false; +} + +void RenderObject::sortRenderObjects() { + Common::sort(_children.begin(), _children.end(), greater); +} + +void RenderObject::updateAbsolutePos() { + calcAbsolutePos(_absoluteX, _absoluteY); + + RENDEROBJECT_ITER it = _children.begin(); + for (; it != _children.end(); ++it) + (*it)->updateAbsolutePos(); +} + +bool RenderObject::getObjectIntersection(RenderObjectPtr<RenderObject> pObject, Common::Rect &result) { + result = pObject->getBbox(); + result.clip(_bbox); + return result.isValidRect(); +} + +void RenderObject::setPos(int x, int y) { + _x = x; + _y = y; + updateAbsolutePos(); +} + +void RenderObject::setX(int x) { + _x = x; + updateAbsolutePos(); +} + +void RenderObject::setY(int y) { + _y = y; + updateAbsolutePos(); +} + +void RenderObject::setZ(int z) { + if (z < 0) + BS_LOG_ERRORLN("Tried to set a negative Z value (%d).", z); + else + _z = z; +} + +void RenderObject::setVisible(bool visible) { + _visible = visible; +} + +RenderObjectPtr<Animation> RenderObject::addAnimation(const Common::String &filename) { + RenderObjectPtr<Animation> aniPtr((new Animation(this->getHandle(), filename))->getHandle()); + if (aniPtr.isValid() && aniPtr->getInitSuccess()) + return aniPtr; + else { + if (aniPtr.isValid()) + aniPtr.erase(); + return RenderObjectPtr<Animation>(); + } +} + +RenderObjectPtr<Animation> RenderObject::addAnimation(const AnimationTemplate &animationTemplate) { + Animation *aniPtr = new Animation(this->getHandle(), animationTemplate); + if (aniPtr && aniPtr->getInitSuccess()) + return aniPtr->getHandle(); + else { + delete aniPtr; + return RenderObjectPtr<Animation>(); + } +} + +RenderObjectPtr<Bitmap> RenderObject::addBitmap(const Common::String &filename) { + RenderObjectPtr<Bitmap> bitmapPtr((new StaticBitmap(this->getHandle(), filename))->getHandle()); + if (bitmapPtr.isValid() && bitmapPtr->getInitSuccess()) + return RenderObjectPtr<Bitmap>(bitmapPtr); + else { + if (bitmapPtr.isValid()) + bitmapPtr.erase(); + return RenderObjectPtr<Bitmap>(); + } +} + +RenderObjectPtr<Bitmap> RenderObject::addDynamicBitmap(uint width, uint height) { + RenderObjectPtr<Bitmap> bitmapPtr((new DynamicBitmap(this->getHandle(), width, height))->getHandle()); + if (bitmapPtr.isValid() && bitmapPtr->getInitSuccess()) + return bitmapPtr; + else { + if (bitmapPtr.isValid()) + bitmapPtr.erase(); + return RenderObjectPtr<Bitmap>(); + } +} + +RenderObjectPtr<Panel> RenderObject::addPanel(int width, int height, uint color) { + RenderObjectPtr<Panel> panelPtr((new Panel(this->getHandle(), width, height, color))->getHandle()); + if (panelPtr.isValid() && panelPtr->getInitSuccess()) + return panelPtr; + else { + if (panelPtr.isValid()) + panelPtr.erase(); + return RenderObjectPtr<Panel>(); + } +} + +RenderObjectPtr<Text> RenderObject::addText(const Common::String &font, const Common::String &text) { + RenderObjectPtr<Text> textPtr((new Text(this->getHandle()))->getHandle()); + if (textPtr.isValid() && textPtr->getInitSuccess() && textPtr->setFont(font)) { + textPtr->setText(text); + return textPtr; + } else { + if (textPtr.isValid()) + textPtr.erase(); + return RenderObjectPtr<Text>(); + } +} + +bool RenderObject::persist(OutputPersistenceBlock &writer) { + // Typ und Handle werden als erstes gespeichert, damit beim Laden ein Objekt vom richtigen Typ mit dem richtigen Handle erzeugt werden kann. + writer.write(static_cast<uint>(_type)); + writer.write(_handle); + + // Restliche Objekteigenschaften speichern. + writer.write(_x); + writer.write(_y); + writer.write(_absoluteX); + writer.write(_absoluteY); + writer.write(_z); + writer.write(_width); + writer.write(_height); + writer.write(_visible); + writer.write(_childChanged); + writer.write(_initSuccess); + writer.write(_bbox.left); + writer.write(_bbox.top); + writer.write(_bbox.right); + writer.write(_bbox.bottom); + writer.write(_oldBbox.left); + writer.write(_oldBbox.top); + writer.write(_oldBbox.right); + writer.write(_oldBbox.bottom); + writer.write(_oldX); + writer.write(_oldY); + writer.write(_oldZ); + writer.write(_oldVisible); + writer.write(_parentPtr.isValid() ? _parentPtr->getHandle() : 0); + writer.write(_refreshForced); + + return true; +} + +bool RenderObject::unpersist(InputPersistenceBlock &reader) { + // Typ und Handle wurden schon von RecreatePersistedRenderObject() ausgelesen. Jetzt werden die restlichen Objekteigenschaften ausgelesen. + reader.read(_x); + reader.read(_y); + reader.read(_absoluteX); + reader.read(_absoluteY); + reader.read(_z); + reader.read(_width); + reader.read(_height); + reader.read(_visible); + reader.read(_childChanged); + reader.read(_initSuccess); + reader.read(_bbox.left); + reader.read(_bbox.top); + reader.read(_bbox.right); + reader.read(_bbox.bottom); + reader.read(_oldBbox.left); + reader.read(_oldBbox.top); + reader.read(_oldBbox.right); + reader.read(_oldBbox.bottom); + reader.read(_oldX); + reader.read(_oldY); + reader.read(_oldZ); + reader.read(_oldVisible); + uint parentHandle; + reader.read(parentHandle); + _parentPtr = RenderObjectPtr<RenderObject>(parentHandle); + reader.read(_refreshForced); + + updateAbsolutePos(); + updateObjectState(); + + return reader.isGood(); +} + +bool RenderObject::persistChildren(OutputPersistenceBlock &writer) { + bool result = true; + + // Kinderanzahl speichern. + writer.write(_children.size()); + + // Rekursiv alle Kinder speichern. + RENDEROBJECT_LIST::iterator it = _children.begin(); + while (it != _children.end()) { + result &= (*it)->persist(writer); + ++it; + } + + return result; +} + +bool RenderObject::unpersistChildren(InputPersistenceBlock &reader) { + bool result = true; + + // Kinderanzahl einlesen. + uint childrenCount; + reader.read(childrenCount); + if (!reader.isGood()) + return false; + + // Alle Kinder rekursiv wieder herstellen. + for (uint i = 0; i < childrenCount; ++i) { + if (!recreatePersistedRenderObject(reader).isValid()) + return false; + } + + return result && reader.isGood(); +} + +RenderObjectPtr<RenderObject> RenderObject::recreatePersistedRenderObject(InputPersistenceBlock &reader) { + RenderObjectPtr<RenderObject> result; + + // Typ und Handle auslesen. + uint type; + uint handle; + reader.read(type); + reader.read(handle); + if (!reader.isGood()) + return result; + + switch (type) { + case TYPE_PANEL: + result = (new Panel(reader, this->getHandle(), handle))->getHandle(); + break; + + case TYPE_STATICBITMAP: + result = (new StaticBitmap(reader, this->getHandle(), handle))->getHandle(); + break; + + case TYPE_DYNAMICBITMAP: + result = (new DynamicBitmap(reader, this->getHandle(), handle))->getHandle(); + break; + + case TYPE_TEXT: + result = (new Text(reader, this->getHandle(), handle))->getHandle(); + break; + + case TYPE_ANIMATION: + result = (new Animation(reader, this->getHandle(), handle))->getHandle(); + break; + + default: + BS_LOG_ERRORLN("Cannot recreate render object of unknown type %d.", type); + } + + return result; +} + +bool RenderObject::greater(const RenderObjectPtr<RenderObject> lhs, const RenderObjectPtr<RenderObject> rhs) { + // Das Objekt mit dem kleinem Z-Wert müssen zuerst gerendert werden. + if (lhs->_z != rhs->_z) + return lhs->_z < rhs->_z; + // Falls der Z-Wert gleich ist, wird das weiter oben gelegenen Objekte zuerst gezeichnet. + return lhs->_y < rhs->_y; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/renderobject.h b/engines/sword25/gfx/renderobject.h new file mode 100644 index 0000000000..7b7b9047f7 --- /dev/null +++ b/engines/sword25/gfx/renderobject.h @@ -0,0 +1,525 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + BS_RenderObject + --------------- + Dieses ist die Klasse die sämtliche sichtbaren Objekte beschreibt. Alle anderen sichtbaren Objekte müssen von ihr abgeleitet werden. + Diese Klasse erledigt Aufgaben wie: minimales Neuzeichnen, Renderreihenfolge, Objekthierachie. + Alle BS_RenderObject Instanzen werden von einem BS_RenderObjectManager in einem Baum verwaltet. + + Autor: Malte Thiesen +*/ + +#ifndef SWORD25_RENDEROBJECT_H +#define SWORD25_RENDEROBJECT_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "common/rect.h" +#include "sword25/gfx/renderobjectptr.h" + +#include "common/list.h" + +namespace Sword25 { + +class Kernel; +class RenderObjectManager; +class Bitmap; +class Animation; +class AnimationTemplate; +class Panel; +class Text; + +// Klassendefinition +/** + @brief Dieses ist die Klasse die sämtliche sichtbaren Objekte beschreibt. + + Alle anderen sichtbaren Objekte müssen von ihr abgeleitet werden. + Diese Klasse erledigt Aufgaben wie: minimales Neuzeichnen, Renderreihenfolge, Objekthierachie. + Alle BS_RenderObject Instanzen werden von einem BS_RenderObjektManager in einem Baum verwaltet. + */ +class RenderObject { +public: + // Konstanten + // ---------- + enum TYPES { + /// Das Wurzelobjekt. Siehe BS_RenderObjectManager + TYPE_ROOT, + /// Ein Image. Siehe BS_Bitmap. + TYPE_STATICBITMAP, + TYPE_DYNAMICBITMAP, + /// Eine Animation. Siehe BS_Animation. + TYPE_ANIMATION, + /// Eine farbige Fläche. Siehe BS_Panel. + TYPE_PANEL, + /// Ein Text. Siehe BS_Text. + TYPE_TEXT, + /// Ein unbekannter Objekttyp. Diesen Typ sollte kein Renderobjekt annehmen. + TYPE_UNKNOWN + }; + + // Add-Methoden + // ------------ + + /** + @brief Erzeugt ein Bitmap als Kinderobjekt des Renderobjektes. + @param FileName der Dateiname der Quellbilddatei + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Bitmap> addBitmap(const Common::String &fileName); + /** + @brief Erzeugt ein veränderbares Bitmap als Kinderobjekt des Renderobjektes. + @param Width die Breite des Bitmaps + @param Height die Höhe des Bitmaps + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Bitmap> addDynamicBitmap(uint width, uint height); + /** + @brief Erzeugt eine Animation auf Basis einer Animationsdatei als Kinderobjekt des Renderobjektes. + @param FileName der Dateiname der Quelldatei + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Animation> addAnimation(const Common::String &fileName); + /** + @brief Erzeugt eine Animation auf Basis eines Animationstemplate als Kinderobjekt des Renderobjektes. + @param pAnimationTemplate ein Pointer auf das Animationstemplate + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + @remark Das Renderobjekt übernimmt die Verwaltung des Animationstemplate. + */ + RenderObjectPtr<Animation> addAnimation(const AnimationTemplate &animationTemplate); + /** + @brief Erzeugt ein neues Farbpanel als Kinderobjekt des Renderobjektes. + @param Width die Breite des Panels + @param Height die Höhe des Panels + @param Color die Farbe des Panels.<br> + Der Standardwert ist Schwarz (BS_RGB(0, 0, 0)). + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + + RenderObjectPtr<Panel> addPanel(int width, int height, uint color = 0xff000000); + /** + @brief Erzeugt ein Textobjekt als Kinderobjekt des Renderobjektes. + @param Font der Dateiname des zu verwendenen Fonts + @param Text der anzuzeigende Text.<br> + Der Standardwert ist "". + @return Gibt einen BS_RenderObjectPtr auf das erzeugte Objekt zurück.<br> + Falls ein Fehler aufgetreten ist wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Text> addText(const Common::String &font, const Common::String &text = ""); + + // Cast-Methoden + // ------------- + /** + @brief Castet das Objekt zu einem BS_Bitmap-Objekt wenn zulässig. + @return Gibt einen BS_RenderObjectPtr auf das Objekt zurück.<br> + Falls der Cast nicht zulässig ist, wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Bitmap> toBitmap() { + if (_type == TYPE_STATICBITMAP || _type == TYPE_DYNAMICBITMAP) + return RenderObjectPtr<Bitmap>(this->getHandle()); + else + return RenderObjectPtr<Bitmap>(); + } + /** + @brief Castet das Objekt zu einem BS_Animation-Objekt wenn zulässig. + @return Gibt einen BS_RenderObjectPtr auf das Objekt zurück.<br> + Falls der Cast nicht zulässig ist, wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Animation> toAnimation() { + if (_type == TYPE_ANIMATION) + return RenderObjectPtr<Animation>(this->getHandle()); + else + return RenderObjectPtr<Animation>(); + } + /** + @brief Castet das Objekt zu einem BS_Panel-Objekt wenn zulässig. + @return Gibt einen BS_RenderObjectPtr auf das Objekt zurück.<br> + Falls der Cast nicht zulässig ist, wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Panel> toPanel() { + if (_type == TYPE_PANEL) + return RenderObjectPtr<Panel>(this->getHandle()); + else + return RenderObjectPtr<Panel>(); + } + /** + @brief Castet das Object zu einem BS_Text-Objekt wenn zulässig. + @return Gibt einen BS_RenderObjectPtr auf das Objekt zurück.<br> + Falls der Cast nicht zulässig ist, wird ein ungültiger BS_RenderObjectPtr zurückgegeben. + */ + RenderObjectPtr<Text> toText() { + if (_type == TYPE_TEXT) + return RenderObjectPtr<Text>(this->getHandle()); + else + return RenderObjectPtr<Text>(); + } + + // Konstruktor / Desktruktor + // ------------------------- + /** + @brief Erzeugt ein neues BS_RenderObject. + @param pKernel ein Pointer auf den Kernel + @param pParent ein Pointer auf das Elternobjekt des neuen Objektes im Objektbaum.<br> + Der Pointer darf nicht NULL sein. + @param Type der Objekttyp<br> + Der Typ BS_RenderObject::TYPE_ROOT ist nicht zulässig. Wurzelknoten müssen mit dem alternativen Konstruktor erzeugt + werden. + @param Handle das Handle, welches dem Objekt zugewiesen werden soll.<br> + Dieser Parameter erzwingt ein bestimmtes Handle für das neue Objekt, oder wählt automatisch ein Handle, wenn der Parameter 0 ist. + Ist das gewünschte Handle bereits vergeben, gibt GetInitSuccess() false zurück.<br> + Der Standardwert ist 0. + @remark Nach dem Aufruf des Konstruktors kann über die Methode GetInitSuccess() abgefragt werden, ob die Konstruktion erfolgreich war.<br> + Es ist nicht notwendig alle BS_RenderObject Instanzen einzeln zu löschen. Dieses geschiet automatisch beim Löschen eines + Vorfahren oder beim Löschen des zuständigen BS_RenderObjectManager. + */ + RenderObject(RenderObjectPtr<RenderObject> pParent, TYPES type, uint handle = 0); + virtual ~RenderObject(); + + // Interface + // --------- + /** + @brief Rendert des Objekt und alle seine Unterobjekte. + @return Gibt false zurück, falls beim Rendern ein Fehler aufgetreten ist. + @remark Vor jedem Aufruf dieser Methode muss ein Aufruf von UpdateObjectState() erfolgt sein. + Dieses kann entweder direkt geschehen oder durch den Aufruf von UpdateObjectState() an einem Vorfahren-Objekt.<br> + Diese Methode darf nur von BS_RenderObjectManager aufgerufen werden. + */ + bool render(); + /** + @brief Bereitet das Objekt und alle seine Unterobjekte auf einen Rendervorgang vor. + Hierbei werden alle Dirty-Rectangles berechnet und die Renderreihenfolge aktualisiert. + @return Gibt false zurück, falls ein Fehler aufgetreten ist. + @remark Diese Methode darf nur von BS_RenderObjectManager aufgerufen werden. + */ + bool updateObjectState(); + /** + @brief Löscht alle Kinderobjekte. + */ + void deleteAllChildren(); + + // Accessor-Methoden + // ----------------- + /** + @brief Setzt die Position des Objektes. + @param X die neue X-Koordinate des Objektes relativ zum Elternobjekt. + @param Y die neue Y-Koordinate des Objektes relativ zum Elternobjekt. + */ + virtual void setPos(int x, int y); + /** + @brief Setzt die Position des Objektes auf der X-Achse. + @param X die neue X-Koordinate des Objektes relativ zum Elternobjekt. + */ + virtual void setX(int x); + /** + @brief Setzt die Position des Objektes auf der Y-Achse. + @param Y die neue Y-Koordinate des Objektes relativ zum Elternobjekt. + */ + virtual void setY(int y); + /** + @brief Setzt den Z-Wert des Objektes. + @param Z der neue Z-Wert des Objektes relativ zum Elternobjekt<br> + Negative Z-Werte sind nicht zulässig. + @remark Der Z-Wert legt die Renderreihenfolge der Objekte fest. Objekte mit niedrigem Z-Wert werden vor Objekten mit höherem + Z-Wert gezeichnet. Je höher der Z-Wert desto weiter "vorne" liegt ein Objekt also.<br> + Wie alle andere Attribute ist auch dieses relativ zum Elternobjekt, ein Kinderobjekt kann also nie unter seinem + Elternobjekt liegen, auch wenn es einen Z-Wert von 0 hat. + */ + virtual void setZ(int z); + /** + @brief Setzt die Sichtbarkeit eine Objektes. + @param Visible der neue Sichtbarkeits-Zustand des Objektes<br> + true entspricht sichtbar, false entspricht unsichtbar. + */ + virtual void setVisible(bool visible); + /** + @brief Gibt die Position des Objektes auf der X-Achse relativ zum Elternobjekt zurück. + */ + virtual int getX() const { + return _x; + } + /** + @brief Gibt die Position des Objektes auf der Y-Achse relativ zum Elternobjekt zurück. + */ + virtual int getY() const { + return _y; + } + /** + @brief Gibt die absolute Position des Objektes auf der X-Achse zurück. + */ + virtual int getAbsoluteX() const { + return _absoluteX; + } + /** + @brief Gibt die absolute Position des Objektes auf der Y-Achse zurück. + */ + virtual int getAbsoluteY() const { + return _absoluteY; + } + /** + @brief Gibt den Z-Wert des Objektes relativ zum Elternobjekt zurück. + @remark Der Z-Wert legt die Renderreihenfolge der Objekte fest. Objekte mit niedrigem Z-Wert werden vor Objekten mit höherem + Z-Wert gezeichnet. Je höher der Z-Wert desto weiter "vorne" liegt ein Objekt also.<br> + Wie alle andere Attribute ist auch dieses relativ zum Elternobjekt, ein Kinderobjekt kann also nie unter seinem + Elternobjekt liegen, auch wenn es einen Z-Wert von 0 hat. + */ + int getZ() const { + return _z; + } + /** + @brief Gibt die Breite des Objektes zurück. + */ + int getWidth() const { + return _width; + } + /** + @brief Gibt die Höhe des Objektes zurück. + */ + int getHeight() const { + return _height; + } + /** + @brief Gibt den Sichtbarkeitszustand des Objektes zurück. + @return Gibt den Sichtbarkeitszustand des Objektes zurück.<br> + true entspricht sichtbar, false entspricht unsichtbar. + */ + bool isVisible() const { + return _visible; + } + /** + @brief Gibt den Typ des Objektes zurück. + */ + TYPES getType() const { + return _type; + } + /** + @brief Gibt zurück, ob das Objekt erfolgreich initialisiert wurde. + @remark Hässlicher Workaround um das Problem, dass Konstruktoren keine Rückgabewerte haben. + */ + bool getInitSuccess() const { + return _initSuccess; + } + /** + @brief Gibt die Bounding-Box des Objektes zurück. + @remark Diese Angabe erfolgt ausnahmsweise in Bildschirmkoordianten und nicht relativ zum Elternobjekt. + */ + const Common::Rect &getBbox() const { + return _bbox; + } + /** + @brief Stellt sicher, dass das Objekt im nächsten Frame neu gezeichnet wird. + */ + void forceRefresh() { + _refreshForced = true; + } + /** + @brief Gibt das Handle des Objekte zurück. + */ + uint getHandle() const { + return _handle; + } + + // Persistenz-Methoden + // ------------------- + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + // TODO: Evtl. protected + bool persistChildren(OutputPersistenceBlock &writer); + bool unpersistChildren(InputPersistenceBlock &reader); + // TODO: Evtl. private + RenderObjectPtr<RenderObject> recreatePersistedRenderObject(InputPersistenceBlock &reader); + +protected: + // Typen + // ----- + typedef Common::List<RenderObjectPtr<RenderObject> > RENDEROBJECT_LIST; + typedef Common::List<RenderObjectPtr<RenderObject> >::iterator RENDEROBJECT_ITER; + + int _x; ///< Die X-Position des Objektes relativ zum Eltern-Objekt + int _y; ///< Die Y-Position des Objektes relativ zum Eltern-Objekt + int _absoluteX; ///< Die absolute X-Position des Objektes + int _absoluteY; ///< Die absolute Y-Position des Objektes + int _z; ///< Der Z-Wert des Objektes relativ zum Eltern-Objekt + int _width; ///< Die Breite des Objektes + int _height; ///< Die Höhe des Objektes + bool _visible; ///< Ist true, wenn das Objekt sichtbar ist + bool _childChanged; ///< Ist true, wenn sich ein Kinderobjekt verändert hat + TYPES _type; ///< Der Objekttyp + bool _initSuccess; ///< Ist true, wenn Objekt erfolgreich intialisiert werden konnte + Common::Rect _bbox; ///< Die Bounding-Box des Objektes in Bildschirmkoordinaten + + // Kopien der Variablen, die für die Errechnung des Dirty-Rects und zur Bestimmung der Objektveränderung notwendig sind + Common::Rect _oldBbox; + int _oldX; + int _oldY; + int _oldZ; + bool _oldVisible; + + /// Ein Pointer auf den BS_RenderObjektManager, der das Objekt verwaltet. + RenderObjectManager *_managerPtr; + + // Render-Methode + // -------------- + /** + @brief Einschubmethode, die den tatsächlichen Redervorgang durchführt. + + Diese Methode wird von Render() aufgerufen um das Objekt darzustellen. + Diese Methode sollte von allen Klassen implementiert werden, die von BS_RederObject erben, um das Zeichnen umzusetzen. + + @return Gibt false zurück, falls das Rendern fehlgeschlagen ist. + @remark + */ + virtual bool doRender() = 0; // { return true; }; + + // RenderObject-Baum Variablen + // --------------------------- + // Der Baum legt die hierachische Ordnung der BS_RenderObjects fest. + // Alle Eigenschaften wie X, Y, Z und Visible eines BS_RenderObjects sind relativ zu denen seines Vaters. + // Außerdem sind die Objekte von links nach rechts in ihrer Renderreihenfolge sortiert. + // Das primäre Sortierkriterium ist hierbei der Z-Wert und das sekundäre der Y-Wert (von oben nach unten). + // Beispiel: + // Screen + // / | \. + // / | \. + // / | \. + // / | \. + // Background Interface Maus + // / \ / | \. + // / \ / | \. + // George Tür Icn1 Icn2 Icn3 + // + // Wenn jetzt das Interface mit SetVisible() ausgeblendet würde, verschwinden auch die Icons, die sich im Interface + // befinden. + // Wenn der Hintergrund bewegt wird (Scrolling), bewegen sich auch die darauf befindlichen Gegenstände und Personen. + + /// Ein Pointer auf das Elternobjekt. + RenderObjectPtr<RenderObject> _parentPtr; + /// Die Liste der Kinderobjekte nach der Renderreihenfolge geordnet + RENDEROBJECT_LIST _children; + + /** + @brief Gibt einen Pointer auf den BS_RenderObjektManager zurück, der das Objekt verwaltet. + */ + RenderObjectManager *getManager() const { + return _managerPtr; + } + /** + @brief Fügt dem Objekt ein neues Kinderobjekt hinzu. + @param pObject ein Pointer auf das einzufügende Objekt + @return Gibt false zurück, falls das Objekt nicht eingefügt werden konnte. + */ + bool addObject(RenderObjectPtr<RenderObject> pObject); + +private: + /// Ist true, wenn das Objekt in nächsten Frame neu gezeichnet werden soll + bool _refreshForced; + + uint _handle; + + /** + @brief Entfernt ein Objekt aus der Kinderliste. + @param pObject ein Pointer auf das zu entfernende Objekt + @return Gibt false zurück, falls das zu entfernende Objekt nicht in der Liste gefunden werden konnte. + */ + bool detatchChildren(RenderObjectPtr<RenderObject> pObject); + /** + @brief Berechnet die Bounding-Box und registriert das Dirty-Rect beim BS_RenderObjectManager. + */ + void updateBoxes(); + /** + @brief Berechnet die Bounding-Box des Objektes. + @return Gibt die Bounding-Box des Objektes in Bildschirmkoordinaten zurück. + */ + Common::Rect calcBoundingBox() const; + /** + @brief Berechnet das Dirty-Rectangle des Objektes. + @return Gibt das Dirty-Rectangle des Objektes in Bildschirmkoordinaten zurück. + */ + Common::Rect calcDirtyRect() const; + /** + @brief Berechnet die absolute Position des Objektes. + */ + void calcAbsolutePos(int &x, int &y) const; + /** + @brief Berechnet die absolute Position des Objektes auf der X-Achse. + */ + int calcAbsoluteX() const; + /** + @brief Berechnet die absolute Position des Objektes. + */ + int calcAbsoluteY() const; + /** + @brief Sortiert alle Kinderobjekte nach ihrem Renderang. + */ + void sortRenderObjects(); + /** + @brief Validiert den Zustand eines Objektes nachdem die durch die Veränderung verursachten Folgen abgearbeitet wurden. + */ + void validateObject(); + /** + @brief Berechnet die absolute Position des Objektes und aller seiner Kinderobjekte neu. + + Diese Methode muss aufgerufen werden, wann immer sich die Position des Objektes verändert. Damit die Kinderobjekte immer die + richtige absolute Position haben. + */ + void updateAbsolutePos(); + /** + @brief Teilt dem Objekt mit, dass sich eines seiner Kinderobjekte dahingehend verändert hat, die eine erneute Bestimmung der + Rendereihenfolge verlangt. + */ + void signalChildChange() { + _childChanged = true; + } + /** + @brief Berechnet des Schnittrechteck der Bounding-Box des Objektes mit einem anderen Objekt. + @param pObjekt ein Pointer auf das Objekt mit dem geschnitten werden soll + @param Result das Ergebnisrechteck + @return Gibt false zurück, falls sich die Objekte gar nicht schneiden. + */ + bool getObjectIntersection(RenderObjectPtr<RenderObject> pObject, Common::Rect &result); + /** + @brief Vergleichsoperator der auf Objektpointern basiert statt auf Objekten. + @remark Diese Methode wird fürs Sortieren der Kinderliste nach der Rendereihenfolge benutzt. + */ + static bool greater(const RenderObjectPtr<RenderObject> lhs, const RenderObjectPtr<RenderObject> rhs); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/renderobjectmanager.cpp b/engines/sword25/gfx/renderobjectmanager.cpp new file mode 100644 index 0000000000..94f7a04b45 --- /dev/null +++ b/engines/sword25/gfx/renderobjectmanager.cpp @@ -0,0 +1,151 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/renderobjectmanager.h" + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/gfx/animationtemplateregistry.h" +#include "common/rect.h" +#include "sword25/gfx/renderobject.h" +#include "sword25/gfx/timedrenderobject.h" +#include "sword25/gfx/rootrenderobject.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "RENDEROBJECTMANAGER" + +RenderObjectManager::RenderObjectManager(int width, int height, int framebufferCount) : + _frameStarted(false) { + // Wurzel des BS_RenderObject-Baumes erzeugen. + _rootPtr = (new RootRenderObject(this, width, height))->getHandle(); +} + +RenderObjectManager::~RenderObjectManager() { + // Die Wurzel des Baumes löschen, damit werden alle BS_RenderObjects mitgelöscht. + _rootPtr.erase(); +} + +void RenderObjectManager::startFrame() { + _frameStarted = true; + + // Verstrichene Zeit bestimmen + int timeElapsed = Kernel::getInstance()->getGfx()->getLastFrameDurationMicro(); + + // Alle BS_TimedRenderObject Objekte über den Framestart und die verstrichene Zeit in Kenntnis setzen + RenderObjectList::iterator iter = _timedRenderObjects.begin(); + for (; iter != _timedRenderObjects.end(); ++iter) + (*iter)->frameNotification(timeElapsed); +} + +bool RenderObjectManager::render() { + // Den Objekt-Status des Wurzelobjektes aktualisieren. Dadurch werden rekursiv alle Baumelemente aktualisiert. + // Beim aktualisieren des Objekt-Status werden auch die Update-Rects gefunden, so dass feststeht, was neu gezeichnet + // werden muss. + if (!_rootPtr.isValid() || !_rootPtr->updateObjectState()) + return false; + + _frameStarted = false; + + // Die Render-Methode der Wurzel aufrufen. Dadurch wird das rekursive Rendern der Baumelemente angestoßen. + return _rootPtr->render(); +} + +void RenderObjectManager::attatchTimedRenderObject(RenderObjectPtr<TimedRenderObject> renderObjectPtr) { + _timedRenderObjects.push_back(renderObjectPtr); +} + +void RenderObjectManager::detatchTimedRenderObject(RenderObjectPtr<TimedRenderObject> renderObjectPtr) { + for (uint i = 0; i < _timedRenderObjects.size(); i++) + if (_timedRenderObjects[i] == renderObjectPtr) { + _timedRenderObjects.remove_at(i); + break; + } +} + +bool RenderObjectManager::persist(OutputPersistenceBlock &writer) { + bool result = true; + + // Alle Kinder des Wurzelknotens speichern. Dadurch werden alle BS_RenderObjects gespeichert rekursiv gespeichert. + result &= _rootPtr->persistChildren(writer); + + writer.write(_frameStarted); + + // Referenzen auf die TimedRenderObjects persistieren. + writer.write(_timedRenderObjects.size()); + RenderObjectList::const_iterator iter = _timedRenderObjects.begin(); + while (iter != _timedRenderObjects.end()) { + writer.write((*iter)->getHandle()); + ++iter; + } + + // Alle BS_AnimationTemplates persistieren. + result &= AnimationTemplateRegistry::instance().persist(writer); + + return result; +} + +bool RenderObjectManager::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + // Alle Kinder des Wurzelknotens löschen. Damit werden alle BS_RenderObjects gelöscht. + _rootPtr->deleteAllChildren(); + + // Alle BS_RenderObjects wieder hestellen. + if (!_rootPtr->unpersistChildren(reader)) + return false; + + reader.read(_frameStarted); + + // Momentan gespeicherte Referenzen auf TimedRenderObjects löschen. + _timedRenderObjects.resize(0); + + // Referenzen auf die TimedRenderObjects wieder herstellen. + uint timedObjectCount; + reader.read(timedObjectCount); + for (uint i = 0; i < timedObjectCount; ++i) { + uint handle; + reader.read(handle); + _timedRenderObjects.push_back(handle); + } + + // Alle BS_AnimationTemplates wieder herstellen. + result &= AnimationTemplateRegistry::instance().unpersist(reader); + + return result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/renderobjectmanager.h b/engines/sword25/gfx/renderobjectmanager.h new file mode 100644 index 0000000000..8511382d6e --- /dev/null +++ b/engines/sword25/gfx/renderobjectmanager.h @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + BS_RenderObjectManager + ---------------------- + Diese Klasse ist für die Verwaltung von BS_RenderObjects zuständig. + + Sie sorgt z.B. dafür, dass die BS_RenderObjects in der richtigen Reihenfolge gerendert werden. + + Autor: Malte Thiesen +*/ + +#ifndef SWORD25_RENDEROBJECTMANAGER_H +#define SWORD25_RENDEROBJECTMANAGER_H + +#include "common/rect.h" +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobjectptr.h" +#include "sword25/kernel/persistable.h" + +namespace Sword25 { + +class Kernel; +class RenderObject; +class TimedRenderObject; + +/** + @brief Diese Klasse ist für die Verwaltung von BS_RenderObjects zuständig. + + Sie sorgt dafür, dass die BS_RenderObjects in der richtigen Reihenfolge gerendert werden und ermöglicht den Zugriff auf die + BS_RenderObjects über einen String. +*/ +class RenderObjectManager : public Persistable { +public: + /** + @brief Erzeugt ein neues BS_RenderObjectManager-Objekt. + @param Width die horizontale Bildschirmauflösung in Pixeln + @param Height die vertikale Bildschirmauflösung in Pixeln + @param Die Anzahl an Framebuffern, die eingesetzt wird (Backbuffer + Primary). + */ + RenderObjectManager(int width, int height, int framebufferCount); + virtual ~RenderObjectManager(); + + // Interface + // --------- + /** + @brief Initialisiert den Manager für einen neuen Frame. + @remark Alle Veränderungen an Objekten müssen nach einem Aufruf dieser Methode geschehen, damit sichergestellt ist, dass diese + visuell umgesetzt werden.<br> + Mit dem Aufruf dieser Methode werden die Rückgabewerte von GetUpdateRects() und GetUpdateRectCount() auf ihre Startwerte + zurückgesetzt. Wenn man also mit diesen Werten arbeiten möchten, muss man dies nach einem Aufruf von Render() und vor + einem Aufruf von StartFrame() tun. + */ + void startFrame(); + /** + @brief Rendert alle Objekte die sich während des letzten Aufrufes von Render() verändert haben. + @return Gibt false zurück, falls das Rendern fehlgeschlagen ist. + */ + bool render(); + /** + @brief Gibt einen Pointer auf die Wurzel des Objektbaumes zurück. + */ + RenderObjectPtr<RenderObject> getTreeRoot() { + return _rootPtr; + } + /** + @brief Fügt ein BS_TimedRenderObject in die Liste der zeitabhängigen Render-Objekte. + + Alle Objekte die sich in dieser Liste befinden werden vor jedem Frame über die seit dem letzten Frame + vergangene Zeit informiert, so dass sich ihren Zustand zeitabhängig verändern können. + + @param RenderObject das einzufügende BS_TimedRenderObject + */ + void attatchTimedRenderObject(RenderObjectPtr<TimedRenderObject> pRenderObject); + /** + @brief Entfernt ein BS_TimedRenderObject aus der Liste für zeitabhängige Render-Objekte. + */ + void detatchTimedRenderObject(RenderObjectPtr<TimedRenderObject> pRenderObject); + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + bool _frameStarted; + typedef Common::Array<RenderObjectPtr<TimedRenderObject> > RenderObjectList; + RenderObjectList _timedRenderObjects; + + // RenderObject-Tree Variablen + // --------------------------- + // Der Baum legt die hierachische Ordnung der BS_RenderObjects fest. + // Zu weiteren Informationen siehe: "renderobject.h" + RenderObjectPtr<RenderObject> _rootPtr; // Die Wurzel der Baumes +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/renderobjectptr.h b/engines/sword25/gfx/renderobjectptr.h new file mode 100644 index 0000000000..c22c6e83e7 --- /dev/null +++ b/engines/sword25/gfx/renderobjectptr.h @@ -0,0 +1,79 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_RENDER_OBJECT_PTR_H +#define SWORD25_RENDER_OBJECT_PTR_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobjectregistry.h" + +namespace Sword25 { + +class RenderObject; + +template<class T> +class RenderObjectPtr { +public: + RenderObjectPtr() : _handle(0) {} + + RenderObjectPtr(uint handle) : _handle(handle) {} + + T *operator->() const { + return static_cast<T *>(RenderObjectRegistry::instance().resolveHandle(_handle)); + } + + bool operator==(const RenderObjectPtr<T> & other) { + return _handle == other._handle; + } + + bool isValid() const { + return RenderObjectRegistry::instance().resolveHandle(_handle) != 0; + } + + void erase() { + delete static_cast<T *>(RenderObjectRegistry::instance().resolveHandle(_handle)); + _handle = 0; + } + +private: + uint _handle; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/renderobjectregistry.cpp b/engines/sword25/gfx/renderobjectregistry.cpp new file mode 100644 index 0000000000..c52e2f1a45 --- /dev/null +++ b/engines/sword25/gfx/renderobjectregistry.cpp @@ -0,0 +1,51 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/renderobjectregistry.h" + +DECLARE_SINGLETON(Sword25::RenderObjectRegistry) + +namespace Sword25 { + +#define BS_LOG_PREFIX "RENDEROBJECTREGISTRY" + +void RenderObjectRegistry::logErrorLn(const char *message) const { + BS_LOG_ERRORLN(message); +} + +void RenderObjectRegistry::logWarningLn(const char *message) const { + BS_LOG_WARNINGLN(message); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/renderobjectregistry.h b/engines/sword25/gfx/renderobjectregistry.h new file mode 100644 index 0000000000..357d041068 --- /dev/null +++ b/engines/sword25/gfx/renderobjectregistry.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_RENDEROBJECTREGISTRY_H +#define SWORD25_RENDEROBJECTREGISTRY_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/objectregistry.h" + +#include "common/singleton.h" + +namespace Sword25 { + +class RenderObject; + +class RenderObjectRegistry : + public ObjectRegistry<RenderObject>, + public Common::Singleton<RenderObjectRegistry> { +private: + virtual void logErrorLn(const char *message) const; + virtual void logWarningLn(const char *message) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/rootrenderobject.h b/engines/sword25/gfx/rootrenderobject.h new file mode 100644 index 0000000000..e4e3fba3c8 --- /dev/null +++ b/engines/sword25/gfx/rootrenderobject.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_ROOTRENDEROBJECT_H +#define SWORD25_ROOTRENDEROBJECT_H + +// Includes +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +// ----------------------------------------------------------------------------- +// Forward Declarations +// ----------------------------------------------------------------------------- + +class Kernel; + +// Klassendefinition +class RenderObjectManager; + +class RootRenderObject : public RenderObject { + friend class RenderObjectManager; + +private: + RootRenderObject(RenderObjectManager *managerPtr, int width, int height) : + RenderObject(RenderObjectPtr<RenderObject>(), TYPE_ROOT) { + _managerPtr = managerPtr; + _width = width; + _height = height; + } + +protected: + virtual bool doRender() { + return true; + } +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/screenshot.cpp b/engines/sword25/gfx/screenshot.cpp new file mode 100644 index 0000000000..88417b72c5 --- /dev/null +++ b/engines/sword25/gfx/screenshot.cpp @@ -0,0 +1,185 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// Disable symbol overrides so that we can use png.h +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#define BS_LOG_PREFIX "SCREENSHOT" + +#include "common/system.h" +#include "common/savefile.h" +#include "sword25/gfx/screenshot.h" +#include "sword25/kernel/filesystemutil.h" +#include <png.h> + +namespace Sword25 { + +#include "common/pack-start.h" +struct RGB_PIXEL { + byte red; + byte green; + byte blue; +} PACKED_STRUCT; +#include "common/pack-end.h" + +void userWriteFn(png_structp png_ptr, png_bytep data, png_size_t length) { + static_cast<Common::WriteStream *>(png_get_io_ptr(png_ptr))->write(data, length); +} + +void userFlushFn(png_structp png_ptr) { +} + +bool Screenshot::saveToFile(Graphics::Surface *data, Common::WriteStream *stream) { + // Reserve buffer space + RGB_PIXEL *pixelBuffer = new RGB_PIXEL[data->w * data->h]; + + // Convert the RGBA data to RGB + const byte *pSrc = (const byte *)data->getBasePtr(0, 0); + RGB_PIXEL *pDest = pixelBuffer; + + for (uint y = 0; y < data->h; y++) { + for (uint x = 0; x < data->w; x++) { + uint32 srcPixel = READ_LE_UINT32(pSrc); + pSrc += sizeof(uint32); + pDest->red = (srcPixel >> 16) & 0xff; + pDest->green = (srcPixel >> 8) & 0xff; + pDest->blue = srcPixel & 0xff; + ++pDest; + } + } + + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + error("Could not create PNG write-struct."); + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + error("Could not create PNG info-struct."); + + // The compression buffer must be large enough to the entire image. + // This ensures that only an IDAT chunk is created. + // When buffer size is used 110% of the raw data size to be sure. + png_set_compression_buffer_size(png_ptr, (data->w * data->h * 3 * 110) / 100); + + // Initialise PNG-Info structure + png_set_IHDR(png_ptr, info_ptr, + data->w, // Width + data->h, // Height + 8, // Bits depth + PNG_COLOR_TYPE_RGB, // Colour type + PNG_INTERLACE_NONE, // No interlacing + PNG_COMPRESSION_TYPE_DEFAULT, // Compression type + PNG_FILTER_TYPE_DEFAULT); // Filter Type + + // Rowpointer erstellen + png_bytep *rowPointers = new png_bytep[data->h]; + for (uint i = 0; i < data->h; i++) { + rowPointers[i] = (png_bytep)&pixelBuffer[data->w * i]; + } + png_set_rows(png_ptr, info_ptr, &rowPointers[0]); + + // Write out the png data to the file + png_set_write_fn(png_ptr, (void *)stream, userWriteFn, userFlushFn); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + png_destroy_write_struct(&png_ptr, &info_ptr); + + delete[] pixelBuffer; + delete[] rowPointers; + + return true; +} + +// ----------------------------------------------------------------------------- + +Common::MemoryReadStream *Screenshot::createThumbnail(Graphics::Surface *data) { + // This method takes a screen image with a dimension of 800x600, and creates a screenshot with a dimension of 200x125. + // First 50 pixels are cut off the top and bottom (the interface boards in the game). The remaining image of 800x500 + // will be on a 16th of its size, reduced by being handed out in 4x4 pixel blocks and the average of each block + // generates a pixel of the target image. Finally, the result as a PNG file is stored as a file. + + // The source image must be 800x600. + if (data->w != 800 || data->h != 600 || data->bytesPerPixel != 4) { + BS_LOG_ERRORLN("The sreenshot dimensions have to be 800x600 in order to be saved as a thumbnail."); + return false; + } + + // Buffer for the output thumbnail + Graphics::Surface thumbnail; + thumbnail.create(200, 125, 4); + + // Über das Zielbild iterieren und einen Pixel zur Zeit berechnen. + uint x, y; + x = y = 0; + + for (byte *pDest = (byte *)thumbnail.pixels; pDest < ((byte *)thumbnail.pixels + thumbnail.pitch * thumbnail.h); ) { + // Get an average over a 4x4 pixel block in the source image + int alpha, red, green, blue; + alpha = red = green = blue = 0; + for (int j = 0; j < 4; ++j) { + const uint32 *srcP = (const uint32 *)data->getBasePtr(x * 4, y * 4 + j + 50); + for (int i = 0; i < 4; ++i) { + uint32 pixel = READ_LE_UINT32(srcP + i); + alpha += (pixel >> 24); + red += (pixel >> 16) & 0xff; + green += (pixel >> 8) & 0xff; + blue += pixel & 0xff; + } + } + + // Write target pixel + *pDest++ = blue / 16; + *pDest++ = green / 16; + *pDest++ = red / 16; + *pDest++ = alpha / 16; + + // Move to next block + ++x; + if (x == thumbnail.w) { + x = 0; + ++y; + } + } + + // Create a PNG representation of the thumbnail data + Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic(); + saveToFile(&thumbnail, stream); + + // Output a MemoryReadStream that encompasses the written data + Common::MemoryReadStream *result = new Common::MemoryReadStream(stream->getData(), stream->size(), + DisposeAfterUse::YES); + return result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/screenshot.h b/engines/sword25/gfx/screenshot.h new file mode 100644 index 0000000000..eefaa1bca6 --- /dev/null +++ b/engines/sword25/gfx/screenshot.h @@ -0,0 +1,51 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_SCREENSHOT_H +#define SWORD25_SCREENSHOT_H + +#include "graphics/surface.h" +#include "sword25/kernel/common.h" + +namespace Sword25 { + +class Screenshot { +public: + static bool saveToFile(Graphics::Surface *data, Common::WriteStream *stream); + static Common::MemoryReadStream *createThumbnail(Graphics::Surface *data); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/staticbitmap.cpp b/engines/sword25/gfx/staticbitmap.cpp new file mode 100644 index 0000000000..3019fe0ac1 --- /dev/null +++ b/engines/sword25/gfx/staticbitmap.cpp @@ -0,0 +1,185 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/staticbitmap.h" +#include "sword25/gfx/bitmapresource.h" +#include "sword25/package/packagemanager.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "STATICBITMAP" + +StaticBitmap::StaticBitmap(RenderObjectPtr<RenderObject> parentPtr, const Common::String &filename) : + Bitmap(parentPtr, TYPE_STATICBITMAP) { + // Das BS_Bitmap konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. + if (!_initSuccess) + return; + + _initSuccess = initBitmapResource(filename); +} + +StaticBitmap::StaticBitmap(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + Bitmap(parentPtr, TYPE_STATICBITMAP, handle) { + _initSuccess = unpersist(reader); +} + +bool StaticBitmap::initBitmapResource(const Common::String &filename) { + // Bild-Resource laden + Resource *resourcePtr = Kernel::getInstance()->getResourceManager()->requestResource(filename); + if (!resourcePtr) { + BS_LOG_ERRORLN("Could not request resource \"%s\".", filename.c_str()); + return false; + } + if (resourcePtr->getType() != Resource::TYPE_BITMAP) { + BS_LOG_ERRORLN("Requested resource \"%s\" is not a bitmap.", filename.c_str()); + return false; + } + + BitmapResource *bitmapPtr = static_cast<BitmapResource *>(resourcePtr); + + // Den eindeutigen Dateinamen zum späteren Referenzieren speichern + _resourceFilename = bitmapPtr->getFileName(); + + // RenderObject Eigenschaften aktualisieren + _originalWidth = _width = bitmapPtr->getWidth(); + _originalHeight = _height = bitmapPtr->getHeight(); + + // Bild-Resource freigeben + bitmapPtr->release(); + + return true; +} + +StaticBitmap::~StaticBitmap() { +} + +bool StaticBitmap::doRender() { + // Bitmap holen + Resource *resourcePtr = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(resourcePtr); + BS_ASSERT(resourcePtr->getType() == Resource::TYPE_BITMAP); + BitmapResource *bitmapResourcePtr = static_cast<BitmapResource *>(resourcePtr); + + // Framebufferobjekt holen + GraphicEngine *gfxPtr = Kernel::getInstance()->getGfx(); + BS_ASSERT(gfxPtr); + + // Bitmap zeichnen + bool result; + if (_scaleFactorX == 1.0f && _scaleFactorY == 1.0f) { + result = bitmapResourcePtr->blit(_absoluteX, _absoluteY, + (_flipV ? BitmapResource::FLIP_V : 0) | + (_flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, -1, -1); + } else { + result = bitmapResourcePtr->blit(_absoluteX, _absoluteY, + (_flipV ? BitmapResource::FLIP_V : 0) | + (_flipH ? BitmapResource::FLIP_H : 0), + 0, _modulationColor, _width, _height); + } + + // Resource freigeben + bitmapResourcePtr->release(); + + return result; +} + +uint StaticBitmap::getPixel(int x, int y) const { + BS_ASSERT(x >= 0 && x < _width); + BS_ASSERT(y >= 0 && y < _height); + + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + BitmapResource *pBitmapResource = static_cast<BitmapResource *>(pResource); + uint result = pBitmapResource->getPixel(x, y); + pResource->release(); + return result; +} + +bool StaticBitmap::setContent(const byte *pixeldata, uint size, uint offset, uint stride) { + BS_LOG_ERRORLN("SetContent() ist not supported with this object."); + return false; +} + +bool StaticBitmap::isAlphaAllowed() const { + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + bool result = static_cast<BitmapResource *>(pResource)->isAlphaAllowed(); + pResource->release(); + return result; +} + +bool StaticBitmap::isColorModulationAllowed() const { + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + bool result = static_cast<BitmapResource *>(pResource)->isColorModulationAllowed(); + pResource->release(); + return result; +} + +bool StaticBitmap::isScalingAllowed() const { + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(_resourceFilename); + BS_ASSERT(pResource->getType() == Resource::TYPE_BITMAP); + bool result = static_cast<BitmapResource *>(pResource)->isScalingAllowed(); + pResource->release(); + return result; +} + +bool StaticBitmap::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= Bitmap::persist(writer); + writer.writeString(_resourceFilename); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +bool StaticBitmap::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= Bitmap::unpersist(reader); + Common::String resourceFilename; + reader.readString(resourceFilename); + result &= initBitmapResource(resourceFilename); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/staticbitmap.h b/engines/sword25/gfx/staticbitmap.h new file mode 100644 index 0000000000..b5b4c4f5a2 --- /dev/null +++ b/engines/sword25/gfx/staticbitmap.h @@ -0,0 +1,81 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_STATIC_BITMAP_H +#define SWORD25_STATIC_BITMAP_H + +#include "sword25/kernel/common.h" +#include "sword25/gfx/bitmap.h" + +namespace Sword25 { + +class StaticBitmap : public Bitmap { + friend class RenderObject; + +private: + /** + @remark Filename muss absoluter Pfad sein + */ + StaticBitmap(RenderObjectPtr<RenderObject> parentPtr, const Common::String &filename); + StaticBitmap(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + +public: + virtual ~StaticBitmap(); + + virtual uint getPixel(int x, int y) const; + + virtual bool setContent(const byte *pixeldata, uint size, uint offset, uint stride); + + virtual bool isScalingAllowed() const; + virtual bool isAlphaAllowed() const; + virtual bool isColorModulationAllowed() const; + virtual bool isSetContentAllowed() const { + return false; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + virtual bool doRender(); + +private: + Common::String _resourceFilename; + + bool initBitmapResource(const Common::String &filename); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/text.cpp b/engines/sword25/gfx/text.cpp new file mode 100644 index 0000000000..b8e12ba570 --- /dev/null +++ b/engines/sword25/gfx/text.cpp @@ -0,0 +1,358 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// TODO: +// Entweder Fontfile absolut abspeichern, oder Verzeichniswechseln verbieten +// Eine relative Fontfile-Angabe könnte verwandt werden nachdem das Verzeichnis bereits gewechselt wurde und die Datei würde nicht mehr gefunden + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/gfx/fontresource.h" +#include "sword25/gfx/bitmapresource.h" + +#include "sword25/gfx/text.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "TEXT" + +namespace { +const uint AUTO_WRAP_THRESHOLD_DEFAULT = 300; +} + +Text::Text(RenderObjectPtr<RenderObject> parentPtr) : + RenderObject(parentPtr, RenderObject::TYPE_TEXT), + _modulationColor(0xffffffff), + _autoWrap(false), + _autoWrapThreshold(AUTO_WRAP_THRESHOLD_DEFAULT) { + +} + +Text::Text(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle) : + RenderObject(parentPtr, TYPE_TEXT, handle), + // Temporarily set fields prior to unpersisting actual values + _modulationColor(0xffffffff), + _autoWrap(false), + _autoWrapThreshold(AUTO_WRAP_THRESHOLD_DEFAULT) { + + // Unpersist the fields + _initSuccess = unpersist(reader); +} + +bool Text::setFont(const Common::String &font) { + // Font precachen. + if (getResourceManager()->precacheResource(font)) { + _font = font; + updateFormat(); + forceRefresh(); + return true; + } else { + BS_LOG_ERRORLN("Could not precache font \"%s\". Font probably does not exist.", font.c_str()); + return false; + } + +} + +void Text::setText(const Common::String &text) { + _text = text; + updateFormat(); + forceRefresh(); +} + +void Text::setColor(uint modulationColor) { + uint newModulationColor = (modulationColor & 0x00ffffff) | (_modulationColor & 0xff000000); + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Text::setAlpha(int alpha) { + BS_ASSERT(alpha >= 0 && alpha < 256); + uint newModulationColor = (_modulationColor & 0x00ffffff) | alpha << 24; + if (newModulationColor != _modulationColor) { + _modulationColor = newModulationColor; + forceRefresh(); + } +} + +void Text::setAutoWrap(bool autoWrap) { + if (autoWrap != _autoWrap) { + _autoWrap = autoWrap; + updateFormat(); + forceRefresh(); + } +} + +void Text::setAutoWrapThreshold(uint autoWrapThreshold) { + if (autoWrapThreshold != _autoWrapThreshold) { + _autoWrapThreshold = autoWrapThreshold; + updateFormat(); + forceRefresh(); + } +} + +bool Text::doRender() { + // Font-Resource locken. + FontResource *fontPtr = lockFontResource(); + if (!fontPtr) + return false; + + // Charactermap-Resource locken. + ResourceManager *rmPtr = getResourceManager(); + BitmapResource *charMapPtr; + { + Resource *pResource = rmPtr->requestResource(fontPtr->getCharactermapFileName()); + if (!pResource) { + BS_LOG_ERRORLN("Could not request resource \"%s\".", fontPtr->getCharactermapFileName().c_str()); + return false; + } + if (pResource->getType() != Resource::TYPE_BITMAP) { + BS_LOG_ERRORLN("Requested resource \"%s\" is not a bitmap.", fontPtr->getCharactermapFileName().c_str()); + return false; + } + + charMapPtr = static_cast<BitmapResource *>(pResource); + } + + // Framebufferobjekt holen. + GraphicEngine *gfxPtr = Kernel::getInstance()->getGfx(); + BS_ASSERT(gfxPtr); + + bool result = true; + Common::Array<Line>::iterator iter = _lines.begin(); + for (; iter != _lines.end(); ++iter) { + // Feststellen, ob überhaupt Buchstaben der aktuellen Zeile vom Update betroffen sind. + Common::Rect checkRect = (*iter).bbox; + checkRect.translate(_absoluteX, _absoluteY); + + // Jeden Buchstaben einzeln Rendern. + int curX = _absoluteX + (*iter).bbox.left; + int curY = _absoluteY + (*iter).bbox.top; + for (uint i = 0; i < (*iter).text.size(); ++i) { + Common::Rect curRect = fontPtr->getCharacterRect((byte)(*iter).text[i]); + + Common::Rect renderRect(curX, curY, curX + curRect.width(), curY + curRect.height()); + int renderX = curX + (renderRect.left - renderRect.left); + int renderY = curY + (renderRect.top - renderRect.top); + renderRect.translate(curRect.left - curX, curRect.top - curY); + result = charMapPtr->blit(renderX, renderY, Image::FLIP_NONE, &renderRect, _modulationColor); + if (!result) + break; + + curX += curRect.width() + fontPtr->getGapWidth(); + } + } + + // Charactermap-Resource freigeben. + charMapPtr->release(); + + // Font-Resource freigeben. + fontPtr->release(); + + return result; +} + +ResourceManager *Text::getResourceManager() { + // Pointer auf den Resource-Manager holen. + return Kernel::getInstance()->getResourceManager(); +} + +FontResource *Text::lockFontResource() { + ResourceManager *rmPtr = getResourceManager(); + + // Font-Resource locken. + FontResource *fontPtr; + { + Resource *resourcePtr = rmPtr->requestResource(_font); + if (!resourcePtr) { + BS_LOG_ERRORLN("Could not request resource \"%s\".", _font.c_str()); + return NULL; + } + if (resourcePtr->getType() != Resource::TYPE_FONT) { + BS_LOG_ERRORLN("Requested resource \"%s\" is not a font.", _font.c_str()); + return NULL; + } + + fontPtr = static_cast<FontResource *>(resourcePtr); + } + + return fontPtr; +} + +void Text::updateFormat() { + FontResource *fontPtr = lockFontResource(); + BS_ASSERT(fontPtr); + + updateMetrics(*fontPtr); + + _lines.resize(1); + if (_autoWrap && (uint) _width >= _autoWrapThreshold && _text.size() >= 2) { + _width = 0; + uint curLineWidth = 0; + uint curLineHeight = 0; + uint curLine = 0; + uint tempLineWidth = 0; + uint lastSpace = 0; // we need at least 1 space character to start a new line... + _lines[0].text = ""; + for (uint i = 0; i < _text.size(); ++i) { + uint j; + tempLineWidth = 0; + lastSpace = 0; + for (j = i; j < _text.size(); ++j) { + if ((byte)_text[j] == ' ') + lastSpace = j; + + const Common::Rect &curCharRect = fontPtr->getCharacterRect((byte)_text[j]); + tempLineWidth += curCharRect.width(); + tempLineWidth += fontPtr->getGapWidth(); + + if ((tempLineWidth >= _autoWrapThreshold) && (lastSpace > 0)) + break; + } + + if (j == _text.size()) // everything in 1 line. + lastSpace = _text.size(); + + curLineWidth = 0; + curLineHeight = 0; + for (j = i; j < lastSpace; ++j) { + _lines[curLine].text += _text[j]; + + const Common::Rect &curCharRect = fontPtr->getCharacterRect((byte)_text[j]); + curLineWidth += curCharRect.width(); + curLineWidth += fontPtr->getGapWidth(); + if ((uint)curCharRect.height() > curLineHeight) + curLineHeight = curCharRect.height(); + } + + _lines[curLine].bbox.right = curLineWidth; + _lines[curLine].bbox.bottom = curLineHeight; + if ((uint)_width < curLineWidth) + _width = curLineWidth; + + if (lastSpace < _text.size()) { + ++curLine; + BS_ASSERT(curLine == _lines.size()); + _lines.resize(curLine + 1); + _lines[curLine].text = ""; + } + + i = lastSpace; + } + + // Bounding-Box der einzelnen Zeilen relativ zur ersten festlegen (vor allem zentrieren). + _height = 0; + Common::Array<Line>::iterator iter = _lines.begin(); + for (; iter != _lines.end(); ++iter) { + Common::Rect &bbox = (*iter).bbox; + bbox.left = (_width - bbox.right) / 2; + bbox.right = bbox.left + bbox.right; + bbox.top = (iter - _lines.begin()) * fontPtr->getLineHeight(); + bbox.bottom = bbox.top + bbox.bottom; + _height += bbox.height(); + } + } else { + // Keine automatische Formatierung, also wird der gesamte Text in nur eine Zeile kopiert. + _lines[0].text = _text; + _lines[0].bbox = Common::Rect(0, 0, _width, _height); + } + + fontPtr->release(); +} + +void Text::updateMetrics(FontResource &fontResource) { + _width = 0; + _height = 0; + + for (uint i = 0; i < _text.size(); ++i) { + const Common::Rect &curRect = fontResource.getCharacterRect((byte)_text[i]); + _width += curRect.width(); + if (i != _text.size() - 1) + _width += fontResource.getGapWidth(); + if (_height < curRect.height()) + _height = curRect.height(); + } +} + +bool Text::persist(OutputPersistenceBlock &writer) { + bool result = true; + + result &= RenderObject::persist(writer); + + writer.write(_modulationColor); + writer.writeString(_font); + writer.writeString(_text); + writer.write(_autoWrap); + writer.write(_autoWrapThreshold); + + result &= RenderObject::persistChildren(writer); + + return result; +} + +bool Text::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + result &= RenderObject::unpersist(reader); + + // Farbe und Alpha einlesen. + reader.read(_modulationColor); + + // Beim Laden der anderen Member werden die Set-Methoden benutzt statt der tatsächlichen Member. + // So wird das Layout automatisch aktualisiert und auch alle anderen notwendigen Methoden ausgeführt. + + Common::String font; + reader.readString(font); + setFont(font); + + Common::String text; + reader.readString(text); + setText(text); + + bool autoWrap; + reader.read(autoWrap); + setAutoWrap(autoWrap); + + uint autoWrapThreshold; + reader.read(autoWrapThreshold); + setAutoWrapThreshold(autoWrapThreshold); + + result &= RenderObject::unpersistChildren(reader); + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/text.h b/engines/sword25/gfx/text.h new file mode 100644 index 0000000000..42c1cd7c5d --- /dev/null +++ b/engines/sword25/gfx/text.h @@ -0,0 +1,169 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_TEXT_H +#define SWORD25_TEXT_H + +#include "sword25/kernel/common.h" +#include "common/rect.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +class Kernel; +class FontResource; +class ResourceManager; + +class Text : public RenderObject { + friend class RenderObject; + +public: + /** + @brief Setzt den Font mit dem der Text dargestellt werden soll. + @param Font der Dateiname der Fontdatei. + @return Gibt false zurück, wenn der Font nicht gefunden wurde. + */ + bool setFont(const Common::String &font); + + /** + @brief Setzt den darzustellenden Text. + @param Text der darzustellende Text + */ + void setText(const Common::String &text); + + /** + @brief Setzt den Alphawert des Textes. + @param Alpha der neue Alphawert des Textes (0 = keine Deckung, 255 = volle Deckung). + */ + void setAlpha(int alpha); + + /** + @brief Legt fest, ob der Text automatisch umgebrochen werden soll. + + Wenn dieses Attribut auf true gesetzt ist, wird der Text umgebrochen, sofern er länger als GetAutoWrapThreshold() ist. + + @param AutoWrap gibt an, ob der automatische Umbruch aktiviert oder deaktiviert werden soll. + @remark Dieses Attribut wird mit dem Wert false initialisiert. + */ + void setAutoWrap(bool autoWrap); + + /** + @brief Legt die Längengrenze des Textes in Pixeln fest, ab der ein automatischer Zeilenumbruch vorgenommen wird. + @remark Dieses Attribut wird mit dem Wert 300 initialisiert. + @remark Eine automatische Formatierung wird nur vorgenommen, wenn diese durch einen Aufruf von SetAutoWrap() aktiviert wurde. + */ + void setAutoWrapThreshold(uint autoWrapThreshold); + + /** + @brief Gibt den dargestellten Text zurück. + */ + const Common::String &getText() { + return _text; + } + + /** + @brief Gibt den Namen das momentan benutzten Fonts zurück. + */ + const Common::String &getFont() { + return _font; + } + + /** + @brief Setzt die Farbe des Textes. + @param Color eine 24-Bit RGB Farbe, die die Farbe des Textes festlegt. + */ + void setColor(uint modulationColor); + + /** + @brief Gibt den Alphawert des Textes zurück. + @return Der Alphawert des Textes (0 = keine Deckung, 255 = volle Deckung). + */ + int getAlpha() const { + return _modulationColor >> 24; + } + + /** + @brief Gibt die Farbe des Textes zurück. + @return Eine 24-Bit RGB Farbe, die die Farbe des Textes angibt. + */ + int getColor() const { + return _modulationColor & 0x00ffffff; + } + + /** + @brief Gibt zurück, ob die automatische Formatierung aktiviert ist. + */ + bool isAutoWrapActive() const { + return _autoWrap; + } + + /** + @brief Gibt die Längengrenze des Textes in Pixeln zurück, ab der eine automatische Formatierung vorgenommen wird. + */ + uint getAutoWrapThreshold() const { + return _autoWrapThreshold; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + virtual bool doRender(); + +private: + Text(RenderObjectPtr<RenderObject> parentPtr); + Text(InputPersistenceBlock &reader, RenderObjectPtr<RenderObject> parentPtr, uint handle); + + uint _modulationColor; + Common::String _font; + Common::String _text; + bool _autoWrap; + uint _autoWrapThreshold; + + struct Line { + Common::Rect bbox; + Common::String text; + }; + + Common::Array<Line> _lines; + + void updateFormat(); + void updateMetrics(FontResource &fontResource); + ResourceManager *getResourceManager(); + FontResource *lockFontResource(); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/gfx/timedrenderobject.cpp b/engines/sword25/gfx/timedrenderobject.cpp new file mode 100644 index 0000000000..eaa9b90d26 --- /dev/null +++ b/engines/sword25/gfx/timedrenderobject.cpp @@ -0,0 +1,52 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/gfx/timedrenderobject.h" + +#include "sword25/gfx/renderobjectmanager.h" + +namespace Sword25 { + +TimedRenderObject::TimedRenderObject(RenderObjectPtr<RenderObject> pParent, TYPES type, uint handle) : + RenderObject(pParent, type, handle) { + BS_ASSERT(getManager()); + getManager()->attatchTimedRenderObject(this->getHandle()); +} + +TimedRenderObject::~TimedRenderObject() { + BS_ASSERT(getManager()); + getManager()->detatchTimedRenderObject(this->getHandle()); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/gfx/timedrenderobject.h b/engines/sword25/gfx/timedrenderobject.h new file mode 100644 index 0000000000..6fee19882a --- /dev/null +++ b/engines/sword25/gfx/timedrenderobject.h @@ -0,0 +1,59 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/common.h" +#include "sword25/gfx/renderobject.h" + +namespace Sword25 { + +/** + @brief +*/ + +class TimedRenderObject : public RenderObject { +public: + TimedRenderObject(RenderObjectPtr<RenderObject> pParent, TYPES type, uint handle = 0); + ~TimedRenderObject(); + + /** + @brief Teilt dem Objekt mit, dass ein neuer Frame begonnen wird. + + Diese Methode wird jeden Frame an jedem BS_TimedRenderObject aufgerufen um diesen zu ermöglichen + ihren Zustand Zeitabhängig zu verändern (z.B. Animationen).<br> + @param int TimeElapsed gibt an wie viel Zeit (in Microsekunden) seit dem letzten Frame vergangen ist. + */ + virtual void frameNotification(int timeElapsed) = 0; +}; + +} // End of namespace Sword25 diff --git a/engines/sword25/input/inputengine.cpp b/engines/sword25/input/inputengine.cpp new file mode 100644 index 0000000000..8dc98ba3fb --- /dev/null +++ b/engines/sword25/input/inputengine.cpp @@ -0,0 +1,283 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "INPUTENGINE" + +#include "common/algorithm.h" +#include "common/events.h" +#include "common/system.h" +#include "common/util.h" +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/input/inputengine.h" + +namespace Sword25 { + +#define DOUBLE_CLICK_TIME 500 +#define DOUBLE_CLICK_RECT_SIZE 4 + +InputEngine::InputEngine(Kernel *pKernel) : + Service(pKernel), + _currentState(0), + _leftMouseDown(false), + _rightMouseDown(false), + _mouseX(0), + _mouseY(0), + _leftDoubleClick(false), + _doubleClickTime(DOUBLE_CLICK_TIME), + _doubleClickRectWidth(DOUBLE_CLICK_RECT_SIZE), + _doubleClickRectHeight(DOUBLE_CLICK_RECT_SIZE), + _lastLeftClickTime(0), + _lastLeftClickMouseX(0), + _lastLeftClickMouseY(0) { + memset(_keyboardState[0], 0, sizeof(_keyboardState[0])); + memset(_keyboardState[1], 0, sizeof(_keyboardState[1])); + _leftMouseState[0] = false; + _leftMouseState[1] = false; + _rightMouseState[0] = false; + _rightMouseState[1] = false; + + if (!registerScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); +} + +InputEngine::~InputEngine() { + unregisterScriptBindings(); +} + +bool InputEngine::init() { + // No initialisation needed + return true; +} + +void InputEngine::update() { + Common::Event event; + + // We keep two sets of keyboard states: The current one, and that of + // the previous frame. This allows us to detect which keys changed + // state. Also, by keeping a single central keystate array, we + // ensure that all script queries for key state during a single + // frame get the same consistent replies. + _currentState ^= 1; + memcpy(_keyboardState[_currentState], _keyboardState[_currentState ^ 1], sizeof(_keyboardState[0])); + + // Loop through processing any pending events + bool handleEvents = true; + while (handleEvents && g_system->getEventManager()->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: + _leftMouseDown = event.type == Common::EVENT_LBUTTONDOWN; + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + handleEvents = false; + break; + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_RBUTTONUP: + _rightMouseDown = event.type == Common::EVENT_RBUTTONDOWN; + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + handleEvents = false; + break; + + case Common::EVENT_MOUSEMOVE: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + break; + + case Common::EVENT_KEYDOWN: + case Common::EVENT_KEYUP: + alterKeyboardState(event.kbd.keycode, (event.type == Common::EVENT_KEYDOWN) ? 0x80 : 0); + break; + + default: + break; + } + } + + _leftMouseState[_currentState] = _leftMouseDown; + _rightMouseState[_currentState] = _rightMouseDown; + + testForLeftDoubleClick(); +} + +bool InputEngine::isLeftMouseDown() { + return _leftMouseDown; +} + +bool InputEngine::isRightMouseDown() { + return _rightMouseDown; +} + +void InputEngine::testForLeftDoubleClick() { + _leftDoubleClick = false; + + // Only bother checking for a double click if the left mouse button was clicked + if (wasLeftMouseDown()) { + // Get the time now + uint now = Kernel::getInstance()->getMilliTicks(); + + // A double click is signalled if + // 1. The two clicks are close enough together + // 2. The mouse cursor hasn't moved much + if (now - _lastLeftClickTime <= _doubleClickTime && + ABS(_mouseX - _lastLeftClickMouseX) <= _doubleClickRectWidth / 2 && + ABS(_mouseY - _lastLeftClickMouseY) <= _doubleClickRectHeight / 2) { + _leftDoubleClick = true; + + // Reset the time and position of the last click, so that clicking is not + // interpreted as the first click of a further double-click + _lastLeftClickTime = 0; + _lastLeftClickMouseX = 0; + _lastLeftClickMouseY = 0; + } else { + // There is no double click. Remember the position and time of the click, + // in case it's the first click of a double-click sequence + _lastLeftClickTime = now; + _lastLeftClickMouseX = _mouseX; + _lastLeftClickMouseY = _mouseY; + } + } +} + +void InputEngine::alterKeyboardState(int keycode, byte newState) { + assert(keycode < ARRAYSIZE(_keyboardState[_currentState])); + _keyboardState[_currentState][keycode] = newState; +} + +bool InputEngine::isLeftDoubleClick() { + return _leftDoubleClick; +} + +bool InputEngine::wasLeftMouseDown() { + return (_leftMouseState[_currentState] == false) && (_leftMouseState[_currentState ^ 1] == true); +} + +bool InputEngine::wasRightMouseDown() { + return (_rightMouseState[_currentState] == false) && (_rightMouseState[_currentState ^ 1] == true); +} + +int InputEngine::getMouseX() { + return _mouseX; +} + +int InputEngine::getMouseY() { + return _mouseY; +} + +bool InputEngine::isKeyDown(uint keyCode) { + assert(keyCode < ARRAYSIZE(_keyboardState[_currentState])); + return (_keyboardState[_currentState][keyCode] & 0x80) != 0; +} + +bool InputEngine::wasKeyDown(uint keyCode) { + assert(keyCode < ARRAYSIZE(_keyboardState[_currentState])); + return ((_keyboardState[_currentState][keyCode] & 0x80) == 0) && + ((_keyboardState[_currentState ^ 1][keyCode] & 0x80) != 0); +} + +void InputEngine::setMouseX(int posX) { + _mouseX = posX; + g_system->warpMouse(_mouseX, _mouseY); +} + +void InputEngine::setMouseY(int posY) { + _mouseY = posY; + g_system->warpMouse(_mouseX, _mouseY); +} + +void InputEngine::setCharacterCallback(CharacterCallback callback) { + _characterCallback = callback; +} + +void InputEngine::setCommandCallback(CommandCallback callback) { + _commandCallback = callback; +} + +void InputEngine::reportCharacter(byte character) { + if (_characterCallback) + (*_characterCallback)(character); +} + +void InputEngine::reportCommand(KEY_COMMANDS command) { + if (_commandCallback) + (*_commandCallback)(command); +} + +bool InputEngine::persist(OutputPersistenceBlock &writer) { + // Write out the number of command callbacks and their names. + // Note: We do this only for compatibility with older engines resp. + // the original engine. + writer.write((uint)1); + writer.writeString("LuaCommandCB"); + + // Write out the number of command callbacks and their names. + // Note: We do this only for compatibility with older engines resp. + // the original engine. + writer.write((uint)1); + writer.writeString("LuaCharacterCB"); + + return true; +} + +bool InputEngine::unpersist(InputPersistenceBlock &reader) { + Common::String callbackFunctionName; + + // Read number of command callbacks and their names. + // Note: We do this only for compatibility with older engines resp. + // the original engine. + uint commandCallbackCount; + reader.read(commandCallbackCount); + assert(commandCallbackCount == 1); + + reader.readString(callbackFunctionName); + assert(callbackFunctionName == "LuaCommandCB"); + + // Read number of character callbacks and their names. + // Note: We do this only for compatibility with older engines resp. + // the original engine. + uint characterCallbackCount; + reader.read(characterCallbackCount); + assert(characterCallbackCount == 1); + + reader.readString(callbackFunctionName); + assert(callbackFunctionName == "LuaCharacterCB"); + + return reader.isGood(); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/input/inputengine.h b/engines/sword25/input/inputengine.h new file mode 100644 index 0000000000..946d6a8e5e --- /dev/null +++ b/engines/sword25/input/inputengine.h @@ -0,0 +1,322 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + * BS_InputEngine + * ------------- + * This is the input interface engine that contains all the methods that an + * input source must implement. + * All input engines must be derived from this class. + * + * Autor: Alex Arnst + */ + +#ifndef SWORD25_INPUTENGINE_H +#define SWORD25_INPUTENGINE_H + +#include "common/keyboard.h" + +#include "sword25/kernel/common.h" +#include "sword25/kernel/service.h" +#include "sword25/kernel/persistable.h" + +namespace Sword25 { + +/// Class definitions + +class InputEngine : public Service, public Persistable { +public: + InputEngine(Kernel *pKernel); + ~InputEngine(); + + // NOTE: These codes are registered in inputengine_script.cpp + // If you add or remove entries of this enum, you must also adjust + // the above file. + enum KEY_CODES { + KEY_BACKSPACE = Common::KEYCODE_BACKSPACE, + KEY_TAB = Common::KEYCODE_TAB, + KEY_CLEAR = Common::KEYCODE_CLEAR, + KEY_RETURN = Common::KEYCODE_RETURN, + KEY_PAUSE = Common::KEYCODE_PAUSE, + KEY_CAPSLOCK = Common::KEYCODE_CAPSLOCK, + KEY_ESCAPE = Common::KEYCODE_ESCAPE, + KEY_SPACE = Common::KEYCODE_SPACE, + KEY_PAGEUP = Common::KEYCODE_PAGEUP, + KEY_PAGEDOWN = Common::KEYCODE_PAGEDOWN, + KEY_END = Common::KEYCODE_END, + KEY_HOME = Common::KEYCODE_HOME, + KEY_LEFT = Common::KEYCODE_LEFT, + KEY_UP = Common::KEYCODE_UP, + KEY_RIGHT = Common::KEYCODE_RIGHT, + KEY_DOWN = Common::KEYCODE_DOWN, + KEY_PRINTSCREEN = Common::KEYCODE_PRINT, + KEY_INSERT = Common::KEYCODE_INSERT, + KEY_DELETE = Common::KEYCODE_DELETE, + KEY_0 = Common::KEYCODE_0, + KEY_1 = Common::KEYCODE_1, + KEY_2 = Common::KEYCODE_2, + KEY_3 = Common::KEYCODE_3, + KEY_4 = Common::KEYCODE_4, + KEY_5 = Common::KEYCODE_5, + KEY_6 = Common::KEYCODE_6, + KEY_7 = Common::KEYCODE_7, + KEY_8 = Common::KEYCODE_8, + KEY_9 = Common::KEYCODE_9, + KEY_A = Common::KEYCODE_a, + KEY_B = Common::KEYCODE_b, + KEY_C = Common::KEYCODE_c, + KEY_D = Common::KEYCODE_d, + KEY_E = Common::KEYCODE_e, + KEY_F = Common::KEYCODE_f, + KEY_G = Common::KEYCODE_g, + KEY_H = Common::KEYCODE_h, + KEY_I = Common::KEYCODE_i, + KEY_J = Common::KEYCODE_j, + KEY_K = Common::KEYCODE_k, + KEY_L = Common::KEYCODE_l, + KEY_M = Common::KEYCODE_m, + KEY_N = Common::KEYCODE_n, + KEY_O = Common::KEYCODE_o, + KEY_P = Common::KEYCODE_p, + KEY_Q = Common::KEYCODE_q, + KEY_R = Common::KEYCODE_r, + KEY_S = Common::KEYCODE_s, + KEY_T = Common::KEYCODE_t, + KEY_U = Common::KEYCODE_u, + KEY_V = Common::KEYCODE_v, + KEY_W = Common::KEYCODE_w, + KEY_X = Common::KEYCODE_x, + KEY_Y = Common::KEYCODE_y, + KEY_Z = Common::KEYCODE_z, + KEY_NUMPAD0 = Common::KEYCODE_KP0, + KEY_NUMPAD1 = Common::KEYCODE_KP1, + KEY_NUMPAD2 = Common::KEYCODE_KP2, + KEY_NUMPAD3 = Common::KEYCODE_KP3, + KEY_NUMPAD4 = Common::KEYCODE_KP4, + KEY_NUMPAD5 = Common::KEYCODE_KP5, + KEY_NUMPAD6 = Common::KEYCODE_KP6, + KEY_NUMPAD7 = Common::KEYCODE_KP7, + KEY_NUMPAD8 = Common::KEYCODE_KP8, + KEY_NUMPAD9 = Common::KEYCODE_KP9, + KEY_MULTIPLY = Common::KEYCODE_KP_MULTIPLY, + KEY_ADD = Common::KEYCODE_KP_PLUS, + KEY_SEPARATOR = Common::KEYCODE_EQUALS, // FIXME: This mapping is just a wild guess!! + KEY_SUBTRACT = Common::KEYCODE_KP_MINUS, + KEY_DECIMAL = Common::KEYCODE_KP_PERIOD, + KEY_DIVIDE = Common::KEYCODE_KP_DIVIDE, + KEY_F1 = Common::KEYCODE_F1, + KEY_F2 = Common::KEYCODE_F2, + KEY_F3 = Common::KEYCODE_F3, + KEY_F4 = Common::KEYCODE_F4, + KEY_F5 = Common::KEYCODE_F5, + KEY_F6 = Common::KEYCODE_F6, + KEY_F7 = Common::KEYCODE_F7, + KEY_F8 = Common::KEYCODE_F8, + KEY_F9 = Common::KEYCODE_F9, + KEY_F10 = Common::KEYCODE_F10, + KEY_F11 = Common::KEYCODE_F11, + KEY_F12 = Common::KEYCODE_F12, + KEY_NUMLOCK = Common::KEYCODE_NUMLOCK, + KEY_SCROLL = Common::KEYCODE_SCROLLOCK, + KEY_LSHIFT = Common::KEYCODE_LSHIFT, + KEY_RSHIFT = Common::KEYCODE_RSHIFT, + KEY_LCONTROL = Common::KEYCODE_LCTRL, + KEY_RCONTROL = Common::KEYCODE_RCTRL + }; + + // NOTE: These codes are registered in inputengine_script.cpp + // If you add or remove entries of this enum, you must also adjust + // the above file. + enum KEY_COMMANDS { + KEY_COMMAND_ENTER = 1, + KEY_COMMAND_LEFT = 2, + KEY_COMMAND_RIGHT = 3, + KEY_COMMAND_HOME = 4, + KEY_COMMAND_END = 5, + KEY_COMMAND_BACKSPACE = 6, + KEY_COMMAND_TAB = 7, + KEY_COMMAND_INSERT = 8, + KEY_COMMAND_DELETE = 9 + }; + + /// -------------------------------------------------------------- + /// THESE METHODS MUST BE IMPLEMENTED BY THE INPUT ENGINE + /// -------------------------------------------------------------- + + /** + * Initialises the input engine + * @return Returns a true on success, otherwise false. + */ + bool init(); + + /** + * Performs a "tick" of the input engine. + * + * This method should be called once per frame. It can be used by implementations + * of the input engine that are not running in their own thread, or to perform + * additional administrative tasks that are needed. + */ + void update(); + + /** + * Returns true if the left mouse button is pressed + */ + bool isLeftMouseDown(); + + /** + * Returns true if the right mouse button is pressed. + */ + bool isRightMouseDown(); + + /** + * Returns true if the left mouse button was pressed and released. + * + * The difference between this and IsLeftMouseDown() is that this only returns + * true when the left mouse button is released. + */ + bool wasLeftMouseDown(); + + /** + * Returns true if the right mouse button was pressed and released. + * + * The difference between this and IsRightMouseDown() is that this only returns + * true when the right mouse button is released. + */ + bool wasRightMouseDown(); + + /** + * Returns true if the left mouse button double click was done + */ + bool isLeftDoubleClick(); + + /** + * Returns the X position of the cursor in pixels + */ + int getMouseX(); + + /** + * Returns the Y position of the cursor in pixels + */ + int getMouseY(); + + /** + * Sets the X position of the cursor in pixels + */ + void setMouseX(int posX); + + /** + * Sets the Y position of the cursor in pixels + */ + void setMouseY(int posY); + + /** + * Returns true if a given key was pressed + * @param KeyCode The key code to be checked + * @return Returns true if the given key is done, otherwise false. + */ + bool isKeyDown(uint keyCode); + + /** + * Returns true if a certain key was pushed and released. + * + * The difference between IsKeyDown() is that this only returns true after the key + * has been released. This method facilitates the retrieval of keys, and reading + * strings that users type. + * @param KeyCode The key code to be checked + */ + bool wasKeyDown(uint keyCode); + + typedef void (*CharacterCallback)(int command); + + /** + * Registers a callback function for keyboard input. + * + * The callback that is registered with this function will be called whenever an + * input key is pressed. A letter entry is different from the query using the + * methods isKeyDown() and wasKeyDown() in the sense that are treated instead + * of actual scan-coded letters. These were taken into account, among other things: + * the keyboard layout, the condition the Shift and Caps Lock keys and the repetition + * of longer holding the key. + * The input of strings by the user through use of callbacks should be implemented. + */ + void setCharacterCallback(CharacterCallback callback); + + typedef void (*CommandCallback)(int command); + + /** + * Registers a callback function for the input of commands that can have influence on the string input + * + * The callback that is registered with this function will be called whenever the input service + * has a key that affects the character string input. This could be the following keys: + * Enter, End, Left, Right, ... + * The input of strings by the user through the use of callbacks should be implemented. + */ + void setCommandCallback(CommandCallback callback); + + void reportCharacter(byte character); + void reportCommand(KEY_COMMANDS command); + + bool persist(OutputPersistenceBlock &writer); + bool unpersist(InputPersistenceBlock &reader); + +private: + bool registerScriptBindings(); + void unregisterScriptBindings(); + +private: + void testForLeftDoubleClick(); + void alterKeyboardState(int keycode, byte newState); + + byte _keyboardState[2][512]; + bool _leftMouseState[2]; + bool _rightMouseState[2]; + uint _currentState; + int _mouseX; + int _mouseY; + bool _leftMouseDown; + bool _rightMouseDown; + bool _leftDoubleClick; + uint _doubleClickTime; + int _doubleClickRectWidth; + int _doubleClickRectHeight; + uint _lastLeftClickTime; + int _lastLeftClickMouseX; + int _lastLeftClickMouseY; + CommandCallback _commandCallback; + CharacterCallback _characterCallback; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/input/inputengine_script.cpp b/engines/sword25/input/inputengine_script.cpp new file mode 100644 index 0000000000..2f16a21377 --- /dev/null +++ b/engines/sword25/input/inputengine_script.cpp @@ -0,0 +1,297 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "common/ptr.h" +#include "common/str.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/kernel.h" +#include "sword25/script/script.h" +#include "sword25/script/luabindhelper.h" +#include "sword25/script/luacallback.h" + +#include "sword25/input/inputengine.h" + +#define BS_LOG_PREFIX "INPUTENGINE" + +namespace Sword25 { + +static void theCharacterCallback(int character); +static void theCommandCallback(int command); + +namespace { +class CharacterCallbackClass : public LuaCallback { +public: + CharacterCallbackClass(lua_State *L) : LuaCallback(L) {} + + Common::String _character; + +protected: + int PreFunctionInvokation(lua_State *L) { + lua_pushstring(L, _character.c_str()); + return 1; + } +}; + +static CharacterCallbackClass *characterCallbackPtr = 0; // FIXME: should be turned into InputEngine member var + +class CommandCallbackClass : public LuaCallback { +public: + CommandCallbackClass(lua_State *L) : LuaCallback(L) { + _command = InputEngine::KEY_COMMAND_BACKSPACE; + } + + InputEngine::KEY_COMMANDS _command; + +protected: + int preFunctionInvokation(lua_State *L) { + lua_pushnumber(L, _command); + return 1; + } +}; + +static CommandCallbackClass *commandCallbackPtr = 0; // FIXME: should be turned into InputEngine member var + +} + +static InputEngine *getIE() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + InputEngine *pIE = pKernel->getInput(); + BS_ASSERT(pIE); + return pIE; +} + +static int init(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushbooleancpp(L, pIE->init()); + return 1; +} + +static int update(lua_State *L) { + InputEngine *pIE = getIE(); + + pIE->update(); + return 0; +} + +static int isLeftMouseDown(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushbooleancpp(L, pIE->isLeftMouseDown()); + return 1; +} + +static int isRightMouseDown(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushbooleancpp(L, pIE->isRightMouseDown()); + return 1; +} + +static int wasLeftMouseDown(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushbooleancpp(L, pIE->wasLeftMouseDown()); + return 1; +} + +static int wasRightMouseDown(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushbooleancpp(L, pIE->wasRightMouseDown()); + return 1; +} + +static int isLeftDoubleClick(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushbooleancpp(L, pIE->isLeftDoubleClick()); + return 1; +} + +static int getMouseX(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushnumber(L, pIE->getMouseX()); + return 1; +} + +static int getMouseY(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushnumber(L, pIE->getMouseY()); + return 1; +} + +static int isKeyDown(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushbooleancpp(L, pIE->isKeyDown((uint)luaL_checknumber(L, 1))); + return 1; +} + +static int wasKeyDown(lua_State *L) { + InputEngine *pIE = getIE(); + + lua_pushbooleancpp(L, pIE->wasKeyDown((uint)luaL_checknumber(L, 1))); + return 1; +} + +static int setMouseX(lua_State *L) { + InputEngine *pIE = getIE(); + + pIE->setMouseX((int)luaL_checknumber(L, 1)); + return 0; +} + +static int setMouseY(lua_State *L) { + InputEngine *pIE = getIE(); + + pIE->setMouseY((int)luaL_checknumber(L, 1)); + return 0; +} + +static void theCharacterCallback(int character) { + characterCallbackPtr->_character = static_cast<byte>(character); + lua_State *L = static_cast<lua_State *>(Kernel::getInstance()->getScript()->getScriptObject()); + characterCallbackPtr->invokeCallbackFunctions(L, 1); +} + +static int registerCharacterCallback(lua_State *L) { + luaL_checktype(L, 1, LUA_TFUNCTION); + characterCallbackPtr->registerCallbackFunction(L, 1); + + return 0; +} + +static int unregisterCharacterCallback(lua_State *L) { + luaL_checktype(L, 1, LUA_TFUNCTION); + characterCallbackPtr->unregisterCallbackFunction(L, 1); + + return 0; +} + +static void theCommandCallback(int command) { + commandCallbackPtr->_command = static_cast<InputEngine::KEY_COMMANDS>(command); + lua_State *L = static_cast<lua_State *>(Kernel::getInstance()->getScript()->getScriptObject()); + commandCallbackPtr->invokeCallbackFunctions(L, 1); +} + +static int registerCommandCallback(lua_State *L) { + luaL_checktype(L, 1, LUA_TFUNCTION); + commandCallbackPtr->registerCallbackFunction(L, 1); + + return 0; +} + +static int unregisterCommandCallback(lua_State *L) { + luaL_checktype(L, 1, LUA_TFUNCTION); + commandCallbackPtr->unregisterCallbackFunction(L, 1); + + return 0; +} + +static const char *PACKAGE_LIBRARY_NAME = "Input"; + +static const luaL_reg PACKAGE_FUNCTIONS[] = { + {"Init", init}, + {"Update", update}, + {"IsLeftMouseDown", isLeftMouseDown}, + {"IsRightMouseDown", isRightMouseDown}, + {"WasLeftMouseDown", wasLeftMouseDown}, + {"WasRightMouseDown", wasRightMouseDown}, + {"IsLeftDoubleClick", isLeftDoubleClick}, + {"GetMouseX", getMouseX}, + {"GetMouseY", getMouseY}, + {"SetMouseX", setMouseX}, + {"SetMouseY", setMouseY}, + {"IsKeyDown", isKeyDown}, + {"WasKeyDown", wasKeyDown}, + {"RegisterCharacterCallback", registerCharacterCallback}, + {"UnregisterCharacterCallback", unregisterCharacterCallback}, + {"RegisterCommandCallback", registerCommandCallback}, + {"UnregisterCommandCallback", unregisterCommandCallback}, + {0, 0} +}; + +#define X(k) {"KEY_" #k, InputEngine::KEY_##k} +#define Y(k) {"KEY_COMMAND_" #k, InputEngine::KEY_COMMAND_##k} +static const lua_constant_reg PACKAGE_CONSTANTS[] = { + X(BACKSPACE), X(TAB), X(CLEAR), X(RETURN), X(PAUSE), X(CAPSLOCK), X(ESCAPE), X(SPACE), X(PAGEUP), X(PAGEDOWN), X(END), X(HOME), X(LEFT), + X(UP), X(RIGHT), X(DOWN), X(PRINTSCREEN), X(INSERT), X(DELETE), X(0), X(1), X(2), X(3), X(4), X(5), X(6), X(7), X(8), X(9), X(A), X(B), + X(C), X(D), X(E), X(F), X(G), X(H), X(I), X(J), X(K), X(L), X(M), X(N), X(O), X(P), X(Q), X(R), X(S), X(T), X(U), X(V), X(W), X(X), X(Y), + X(Z), X(NUMPAD0), X(NUMPAD1), X(NUMPAD2), X(NUMPAD3), X(NUMPAD4), X(NUMPAD5), X(NUMPAD6), X(NUMPAD7), X(NUMPAD8), X(NUMPAD9), X(MULTIPLY), + X(ADD), X(SEPARATOR), X(SUBTRACT), X(DECIMAL), X(DIVIDE), X(F1), X(F2), X(F3), X(F4), X(F5), X(F6), X(F7), X(F8), X(F9), X(F10), X(F11), + X(F12), X(NUMLOCK), X(SCROLL), X(LSHIFT), X(RSHIFT), X(LCONTROL), X(RCONTROL), + Y(ENTER), Y(LEFT), Y(RIGHT), Y(HOME), Y(END), Y(BACKSPACE), Y(TAB), Y(INSERT), Y(DELETE), + {0, 0} +}; +#undef X +#undef Y + +// ----------------------------------------------------------------------------- + +bool InputEngine::registerScriptBindings() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ScriptEngine *pScript = pKernel->getScript(); + BS_ASSERT(pScript); + lua_State *L = static_cast<lua_State *>(pScript->getScriptObject()); + BS_ASSERT(L); + + if (!LuaBindhelper::addFunctionsToLib(L, PACKAGE_LIBRARY_NAME, PACKAGE_FUNCTIONS)) return false; + if (!LuaBindhelper::addConstantsToLib(L, PACKAGE_LIBRARY_NAME, PACKAGE_CONSTANTS)) return false; + + assert(characterCallbackPtr == 0); + characterCallbackPtr = new CharacterCallbackClass(L); + + assert(commandCallbackPtr == 0); + commandCallbackPtr = new CommandCallbackClass(L); + + setCharacterCallback(theCharacterCallback); + setCommandCallback(theCommandCallback); + + return true; +} + +void InputEngine::unregisterScriptBindings() { + delete characterCallbackPtr; + characterCallbackPtr = 0; + + delete commandCallbackPtr; + commandCallbackPtr = 0; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/common.h b/engines/sword25/kernel/common.h new file mode 100644 index 0000000000..7b11fe901f --- /dev/null +++ b/engines/sword25/kernel/common.h @@ -0,0 +1,60 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + * common.h + * ----------- + * This file contains functions or macros that are used across the entire project. + * It is therefore extremely important that this header file be referenced in all + * the other header files in the project. + * + * Autor: Malte Thiesen + */ + +#ifndef SWORD25_COMMON_H +#define SWORD25_COMMON_H + +// Global constants +#define DEBUG + +#define BS_ACTIVATE_LOGGING // When defined, logging is activated + +// Engine Includes +#include "sword25/kernel/log.h" + +#include "common/debug.h" + +#define BS_ASSERT(EXP) assert(EXP) + +#endif diff --git a/engines/sword25/kernel/filesystemutil.cpp b/engines/sword25/kernel/filesystemutil.cpp new file mode 100644 index 0000000000..d05ac922e1 --- /dev/null +++ b/engines/sword25/kernel/filesystemutil.cpp @@ -0,0 +1,87 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "common/config-manager.h" +#include "common/fs.h" +#include "common/savefile.h" +#include "common/system.h" +#include "sword25/kernel/filesystemutil.h" +#include "sword25/kernel/persistenceservice.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "FILESYSTEMUTIL" + +Common::String FileSystemUtil::getUserdataDirectory() { + // FIXME: This code is a hack which bypasses the savefile API, + // and should eventually be removed. + Common::String path = ConfMan.get("savepath"); + + if (path.empty()) { + error("No save path has been defined"); + return ""; + } + + // Return the path + return path; +} + +Common::String FileSystemUtil::getPathSeparator() { + // FIXME: This code is a hack which bypasses the savefile API, + // and should eventually be removed. + return Common::String("/"); +} + +bool FileSystemUtil::fileExists(const Common::String &filename) { + Common::File f; + if (f.exists(filename)) + return true; + + // Check if the file exists in the save folder + Common::FSNode folder(PersistenceService::getSavegameDirectory()); + Common::FSNode fileNode = folder.getChild(getPathFilename(filename)); + return fileNode.exists(); +} + +Common::String FileSystemUtil::getPathFilename(const Common::String &path) { + for (int i = path.size() - 1; i >= 0; --i) { + if ((path[i] == '/') || (path[i] == '\\')) { + return Common::String(&path.c_str()[i + 1]); + } + } + + return path; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/filesystemutil.h b/engines/sword25/kernel/filesystemutil.h new file mode 100644 index 0000000000..38a3fdaa12 --- /dev/null +++ b/engines/sword25/kernel/filesystemutil.h @@ -0,0 +1,101 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + * + * The class BS_FileSystemUtil represents a wrapper for file system specific + * operations that do not have equivalents in the C/C++ libraries. + * + * Each supported platform must implement this interface, and the method + * BS_FileSystemUtil Singleton::instance() + */ + +#ifndef SWORD25_FILESYSTEMUTIL_H +#define SWORD25_FILESYSTEMUTIL_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "common/system.h" +#include "common/str.h" +#include "common/str-array.h" +#include "sword25/kernel/common.h" + +namespace Sword25 { + +// ----------------------------------------------------------------------------- +// Class definitions +// ----------------------------------------------------------------------------- + +class FileSystemUtil { +public: + + /** + * This function returns the name of the directory in which all user data is to be stored. + * + * These are for example Screenshots, game saves, configuration files, log files, ... + * @return Returns the name of the directory for user data. + */ + static Common::String getUserdataDirectory(); + + /** + * @return Returns the path seperator + */ + static Common::String getPathSeparator(); + + /** + * @param Filename The path to a file. + * @return Returns the size of the specified file. If the size could not be + * determined, or the file does not exist, returns -1 + */ + static int32 getFileSize(const Common::String &filename); + + /** + * @param Filename The path to a file. + * @return Returns true if the file exists. + */ + static bool fileExists(const Common::String &filename); + + /** + * Gets the filename from a path and filename + * @param Filename The full path and filename + * @return Returns just the filename + */ + static Common::String getPathFilename(const Common::String &path); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/inputpersistenceblock.cpp b/engines/sword25/kernel/inputpersistenceblock.cpp new file mode 100644 index 0000000000..652c2f362f --- /dev/null +++ b/engines/sword25/kernel/inputpersistenceblock.cpp @@ -0,0 +1,152 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "INPUTPERSISTENCEBLOCK" + +#include "sword25/kernel/inputpersistenceblock.h" + +namespace Sword25 { + +InputPersistenceBlock::InputPersistenceBlock(const void *data, uint dataLength) : + _data(static_cast<const byte *>(data), dataLength), + _errorState(NONE) { + _iter = _data.begin(); +} + +InputPersistenceBlock::~InputPersistenceBlock() { + if (_iter != _data.end()) + BS_LOG_WARNINGLN("Persistence block was not read to the end."); +} + +void InputPersistenceBlock::read(int16 &value) { + signed int v; + read(v); + value = static_cast<int16>(v); +} + +void InputPersistenceBlock::read(signed int &value) { + if (checkMarker(SINT_MARKER)) { + rawRead(&value, sizeof(signed int)); + value = convertEndianessFromStorageToSystem(value); + } else { + value = 0; + } +} + +void InputPersistenceBlock::read(uint &value) { + if (checkMarker(UINT_MARKER)) { + rawRead(&value, sizeof(uint)); + value = convertEndianessFromStorageToSystem(value); + } else { + value = 0; + } +} + +void InputPersistenceBlock::read(float &value) { + if (checkMarker(FLOAT_MARKER)) { + rawRead(&value, sizeof(float)); + value = convertEndianessFromStorageToSystem(value); + } else { + value = 0.0f; + } +} + +void InputPersistenceBlock::read(bool &value) { + if (checkMarker(BOOL_MARKER)) { + uint uintBool; + rawRead(&uintBool, sizeof(float)); + uintBool = convertEndianessFromStorageToSystem(uintBool); + value = uintBool == 0 ? false : true; + } else { + value = 0.0f; + } +} + +void InputPersistenceBlock::readString(Common::String &value) { + value = ""; + + if (checkMarker(STRING_MARKER)) { + uint size; + read(size); + + if (checkBlockSize(size)) { + value = Common::String(reinterpret_cast<const char *>(&*_iter), size); + _iter += size; + } + } +} + +void InputPersistenceBlock::readByteArray(Common::Array<byte> &value) { + if (checkMarker(BLOCK_MARKER)) { + uint size; + read(size); + + if (checkBlockSize(size)) { + value = Common::Array<byte>(_iter, size); + _iter += size; + } + } +} + +void InputPersistenceBlock::rawRead(void *destPtr, size_t size) { + if (checkBlockSize(size)) { + memcpy(destPtr, &*_iter, size); + _iter += size; + } +} + +bool InputPersistenceBlock::checkBlockSize(int size) { + if (_data.end() - _iter >= size) { + return true; + } else { + _errorState = END_OF_DATA; + BS_LOG_ERRORLN("Unexpected end of persistence block."); + return false; + } +} + +bool InputPersistenceBlock::checkMarker(byte marker) { + if (!isGood() || !checkBlockSize(1)) + return false; + + if (*_iter++ == marker) { + return true; + } else { + _errorState = OUT_OF_SYNC; + BS_LOG_ERRORLN("Wrong type marker found in persistence block."); + return false; + } +} + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/inputpersistenceblock.h b/engines/sword25/kernel/inputpersistenceblock.h new file mode 100644 index 0000000000..f6ab256460 --- /dev/null +++ b/engines/sword25/kernel/inputpersistenceblock.h @@ -0,0 +1,82 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_INPUTPERSISTENCEBLOCK_H +#define SWORD25_INPUTPERSISTENCEBLOCK_H + +#include "common/array.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistenceblock.h" + +namespace Sword25 { + +class InputPersistenceBlock : public PersistenceBlock { +public: + enum ErrorState { + NONE, + END_OF_DATA, + OUT_OF_SYNC + }; + + InputPersistenceBlock(const void *data, uint dataLength); + virtual ~InputPersistenceBlock(); + + void read(int16 &value); + void read(signed int &value); + void read(uint &value); + void read(float &value); + void read(bool &value); + void readString(Common::String &value); + void readByteArray(Common::Array<byte> &value); + + bool isGood() const { + return _errorState == NONE; + } + ErrorState getErrorState() const { + return _errorState; + } + +private: + bool checkMarker(byte marker); + bool checkBlockSize(int size); + void rawRead(void *destPtr, size_t size); + + Common::Array<byte> _data; + Common::Array<byte>::const_iterator _iter; + ErrorState _errorState; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/kernel.cpp b/engines/sword25/kernel/kernel.cpp new file mode 100644 index 0000000000..9bd8ac5ff7 --- /dev/null +++ b/engines/sword25/kernel/kernel.cpp @@ -0,0 +1,211 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "common/system.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/fmv/movieplayer.h" +#include "sword25/input/inputengine.h" +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/persistenceservice.h" +#include "sword25/math/geometry.h" +#include "sword25/package/packagemanager.h" +#include "sword25/script/luascript.h" +#include "sword25/sfx/soundengine.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "KERNEL" + +Kernel *Kernel::_instance = 0; + +Kernel::Kernel() : + _resourceManager(NULL), + _initSuccess(false), + _gfx(0), + _sfx(0), + _input(0), + _package(0), + _script(0), + _fmv(0) + { + + // Log that the kernel is beign created + BS_LOGLN("created."); + _instance = this; + + // Create the resource manager + _resourceManager = new ResourceManager(this); + + // Initialise the script engine + _script = new LuaScriptEngine(this); + if (!_script || !_script->init()) { + _initSuccess = false; + return; + } + + // Register kernel script bindings + if (!registerScriptBindings()) { + BS_LOG_ERRORLN("Script bindings could not be registered."); + _initSuccess = false; + return; + } + BS_LOGLN("Script bindings registered."); + + _input = new InputEngine(this); + assert(_input); + + _gfx = new GraphicEngine(this); + assert(_gfx); + + _sfx = new SoundEngine(this); + assert(_sfx); + + _package = new PackageManager(this); + assert(_package); + + _geometry = new Geometry(this); + assert(_geometry); + +#ifdef USE_THEORADEC + _fmv = new MoviePlayer(this); + assert(_fmv); +#endif + + _initSuccess = true; +} + +Kernel::~Kernel() { + // Services are de-registered in reverse order of creation + + delete _input; + _input = 0; + + delete _gfx; + _gfx = 0; + + delete _sfx; + _sfx = 0; + + delete _package; + _package = 0; + + delete _geometry; + _geometry = 0; + +#ifdef USE_THEORADEC + delete _fmv; + _fmv = 0; +#endif + + delete _script; + _script = 0; + + // Resource-Manager freigeben + delete _resourceManager; + + BS_LOGLN("destroyed."); +} + +/** + * Returns a random number + * @param Min The minimum allowed value + * @param Max The maximum allowed value + */ +int Kernel::getRandomNumber(int min, int max) { + BS_ASSERT(min <= max); + + return min + _rnd.getRandomNumber(max - min + 1); +} + +/** + * Returns the elapsed time since startup in milliseconds + */ +uint Kernel::getMilliTicks() { + return g_system->getMillis(); +} + +/** + * Returns how much memory is being used + */ +size_t Kernel::getUsedMemory() { + return 0; +} + +/** + * Returns a pointer to the active Gfx Service, or NULL if no Gfx service is active. + */ +GraphicEngine *Kernel::getGfx() { + return _gfx; +} + +/** + * Returns a pointer to the active Sfx Service, or NULL if no Sfx service is active. + */ +SoundEngine *Kernel::getSfx() { + return _sfx; +} + +/** + * Returns a pointer to the active input service, or NULL if no input service is active. + */ +InputEngine *Kernel::getInput() { + return _input; +} + +/** + * Returns a pointer to the active package manager, or NULL if no manager is active. + */ +PackageManager *Kernel::getPackage() { + return _package; +} + +/** + * Returns a pointer to the script engine, or NULL if it is not active. + */ +ScriptEngine *Kernel::getScript() { + return _script; +} + +/** + * Returns a pointer to the movie player, or NULL if it is not active. + */ +MoviePlayer *Kernel::getFMV() { + return _fmv; +} + +void Kernel::sleep(uint msecs) const { + g_system->delayMillis(msecs); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/kernel.h b/engines/sword25/kernel/kernel.h new file mode 100644 index 0000000000..252de64e80 --- /dev/null +++ b/engines/sword25/kernel/kernel.h @@ -0,0 +1,202 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + * BS_Kernel + * --------- + * This is the main class of the engine. + * This class creates and manages all other Engine elements: the sound engine, graphics engine ... + * It is not necessary to release all the items individually, this is performed by the Kernel class. + * + * Autor: Malte Thiesen + */ + +#ifndef SWORD25_KERNEL_H +#define SWORD25_KERNEL_H + +#include "common/scummsys.h" +#include "common/random.h" +#include "common/stack.h" +#include "common/util.h" +#include "engines/engine.h" + +#include "sword25/kernel/common.h" +#include "sword25/kernel/resmanager.h" + +namespace Sword25 { + +// Class definitions +class Service; +class Geometry; +class GraphicEngine; +class ScriptEngine; +class SoundEngine; +class InputEngine; +class PackageManager; +class MoviePlayer; + +/** + * This is the main engine class + * + * This class creates and manages all other engine components such as sound engine, graphics engine ... + * It is not necessary to release all the items individually, this is performed by the Kernel class. +*/ +class Kernel { +public: + + /** + * Returns the elapsed time since startup in milliseconds + */ + uint getMilliTicks(); + + /** + * Specifies whether the kernel was successfully initialised + */ + bool getInitSuccess() const { + return _initSuccess; + } + /** + * Returns a pointer to the BS_ResourceManager + */ + ResourceManager *getResourceManager() { + return _resourceManager; + } + /** + * Returns how much memory is being used + */ + size_t getUsedMemory(); + /** + * Returns a random number + * @param Min The minimum allowed value + * @param Max The maximum allowed value + */ + int getRandomNumber(int min, int max); + /** + * Returns a pointer to the active Gfx Service, or NULL if no Gfx service is active + */ + GraphicEngine *getGfx(); + /** + * Returns a pointer to the active Sfx Service, or NULL if no Sfx service is active + */ + SoundEngine *getSfx(); + /** + * Returns a pointer to the active input service, or NULL if no input service is active + */ + InputEngine *getInput(); + /** + * Returns a pointer to the active package manager, or NULL if no manager is active + */ + PackageManager *getPackage(); + /** + * Returns a pointer to the script engine, or NULL if it is not active + */ + ScriptEngine *getScript(); + + /** + * Returns a pointer to the movie player, or NULL if it is not active + */ + MoviePlayer *getFMV(); + + /** + * Pauses for the specified amount of time + * @param Msecs The amount of time in milliseconds + */ + void sleep(uint msecs) const; + + /** + * Returns the singleton instance for the kernel + */ + static Kernel *getInstance() { + if (!_instance) + _instance = new Kernel(); + return _instance; + } + + /** + * Destroys the kernel instance + * This method should only be called when the game is ended. No subsequent calls to any kernel + * methods should be done after calling this method. + */ + static void deleteInstance() { + if (_instance) { + delete _instance; + _instance = NULL; + } + } + + /** + * Raises an error. This method is used in crashing testing. + */ + void crash() const { + error("Kernel::Crash"); + } + +private: + // ----------------------------------------------------------------------------- + // Constructor / destructor + // Private singleton methods + // ----------------------------------------------------------------------------- + + Kernel(); + virtual ~Kernel(); + + // ----------------------------------------------------------------------------- + // Singleton instance + // ----------------------------------------------------------------------------- + static Kernel *_instance; + + bool _initSuccess; // Specifies whether the engine was set up correctly + + // Random number generator + // ----------------------- + Common::RandomSource _rnd; + + // Resourcemanager + // --------------- + ResourceManager *_resourceManager; + + GraphicEngine *_gfx; + SoundEngine *_sfx; + InputEngine *_input; + PackageManager *_package; + ScriptEngine *_script; + Geometry *_geometry; + MoviePlayer *_fmv; + + bool registerScriptBindings(); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/kernel_script.cpp b/engines/sword25/kernel/kernel_script.cpp new file mode 100644 index 0000000000..d4b9a56d8e --- /dev/null +++ b/engines/sword25/kernel/kernel_script.cpp @@ -0,0 +1,550 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/common.h" +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/filesystemutil.h" +#include "sword25/kernel/resmanager.h" +#include "sword25/kernel/persistenceservice.h" +#include "sword25/script/script.h" +#include "sword25/script/luabindhelper.h" + +namespace Sword25 { + +static int disconnectService(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushboolean(L, true); + + return 1; +} + +static int getActiveServiceIdentifier(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushstring(L, "QUUX"); + + return 1; +} + +static int getSuperclassCount(lua_State *L) { + // This function is only used by a single function in system/kernel.lua which is never called. + lua_pushnumber(L, 0); + + return 1; +} + +static int getSuperclassIdentifier(lua_State *L) { + // This function is only used by a single function in system/kernel.lua which is never called. + lua_pushstring(L, "FOO"); + + return 1; +} + +static int getServiceCount(lua_State *L) { + // This function is only used by a single function in system/kernel.lua which is never called. + lua_pushnumber(L, 0); + + return 1; +} + +static int getServiceIdentifier(lua_State *L) { + // This function is only used by a single function in system/kernel.lua which is never called. + lua_pushstring(L, "BAR"); + + return 1; +} + +static int getMilliTicks(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + + lua_pushnumber(L, pKernel->getMilliTicks()); + + return 1; +} + +static int getTimer(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + + lua_pushnumber(L, static_cast<lua_Number>(pKernel->getMilliTicks()) / 1000.0); + + return 1; +} + +static int startService(lua_State *L) { + // This function is used by system/boot.lua to init all services. + // However, we do nothing here, as we just hard code the init sequence. + lua_pushbooleancpp(L, true); + + return 1; +} + +static int sleep(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + pKernel->sleep(static_cast<uint>(luaL_checknumber(L, 1) * 1000)); + return 0; +} + +static int crash(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + pKernel->crash(); + return 0; +} + +static int executeFile(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ScriptEngine *pSE = pKernel->getScript(); + BS_ASSERT(pSE); + + lua_pushbooleancpp(L, pSE->executeFile(luaL_checkstring(L, 1))); + + return 0; +} + +static int getUserdataDirectory(lua_State *L) { + lua_pushstring(L, FileSystemUtil::getUserdataDirectory().c_str()); + return 1; +} + +static int getPathSeparator(lua_State *L) { + lua_pushstring(L, FileSystemUtil::getPathSeparator().c_str()); + return 1; +} + +static int fileExists(lua_State *L) { + lua_pushbooleancpp(L, FileSystemUtil::fileExists(luaL_checkstring(L, 1))); + return 1; +} + +static int createDirectory(lua_State *L) { + // ScummVM engines cannot create directories, so we do nothing here. + lua_pushbooleancpp(L, false); + return 1; +} + +static int getWinCode(lua_State *L) { + lua_pushstring(L, "ScummVM"); + return 1; +} + +static int getSubversionRevision(lua_State *L) { + // ScummVM is 1337 + lua_pushnumber(L, 1337); + return 1; +} + +static int getUsedMemory(lua_State *L) { + lua_pushnumber(L, Kernel::getInstance()->getUsedMemory()); + return 1; +} + +static const char *KERNEL_LIBRARY_NAME = "Kernel"; + +static const luaL_reg KERNEL_FUNCTIONS[] = { + {"DisconnectService", disconnectService}, + {"GetActiveServiceIdentifier", getActiveServiceIdentifier}, + {"GetSuperclassCount", getSuperclassCount}, + {"GetSuperclassIdentifier", getSuperclassIdentifier}, + {"GetServiceCount", getServiceCount}, + {"GetServiceIdentifier", getServiceIdentifier}, + {"GetMilliTicks", getMilliTicks}, + {"GetTimer", getTimer}, + {"StartService", startService}, + {"Sleep", sleep}, + {"Crash", crash}, + {"ExecuteFile", executeFile}, + {"GetUserdataDirectory", getUserdataDirectory}, + {"GetPathSeparator", getPathSeparator}, + {"FileExists", fileExists}, + {"CreateDirectory", createDirectory}, + {"GetWinCode", getWinCode}, + {"GetSubversionRevision", getSubversionRevision}, + {"GetUsedMemory", getUsedMemory}, + {0, 0} +}; + +static int isVisible(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushbooleancpp(L, true); + + return 1; +} + +static int setVisible(lua_State *L) { + // This function apparently is not used by the game scripts +// pWindow->setVisible(lua_tobooleancpp(L, 1)); + + return 0; +} + +static int getX(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushnumber(L, 0); + + return 1; +} + +static int getY(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushnumber(L, 0); + + return 1; +} + +static int setX(lua_State *L) { + // This is called by system/boot.lua with -1 as value. +// pWindow->setX(static_cast<int>(luaL_checknumber(L, 1))); + + return 0; +} + +static int setY(lua_State *L) { + // This is called by system/boot.lua with -1 as value. +// pWindow->setY(static_cast<int>(luaL_checknumber(L, 1))); + + return 0; +} + +static int getClientX(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushnumber(L, 0); + + return 1; +} + +static int getClientY(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushnumber(L, 0); + + return 1; +} + +static int getWidth(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushnumber(L, 800); + + return 1; +} + +static int getHeight(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushnumber(L, 600); + + return 1; +} + +static int setWidth(lua_State *L) { + // This is called by system/boot.lua with 800 as value. +// pWindow->setWidth(static_cast<int>(luaL_checknumber(L, 1))); + + return 0; +} + +static int setHeight(lua_State *L) { + // This is called by system/boot.lua with 600 as value. +// pWindow->setHeight(static_cast<int>(luaL_checknumber(L, 1))); + + return 0; +} + +static int getTitle(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushstring(L, ""); + + return 1; +} + +static int setTitle(lua_State *L) { + // This is called by system/boot.lua and system/menu.lua, to + // set the window title to the (localized) game name. + // FIXME: Should we call OSystem::setWindowCaption() here? +// pWindow->setTitle(luaL_checkstring(L, 1)); + + return 0; +} + +static int processMessages(lua_State *L) { + // This is called by the main loop in system/boot.lua, + // and the game keeps running as true is returned here. + // It terminates if we return false. + + // TODO: We could do more stuff here if desired... + + // TODO: We could always return true here, and leave quit handling + // to the closeWanted() opcode; see also the TODO comment in there. + + lua_pushbooleancpp(L, !Engine::shouldQuit()); + + return 1; +} + +static int closeWanted(lua_State *L) { + // This is called by system/interface.lua to determine whether the + // user requested the game to close (e.g. by clicking the 'close' widget + // of the game window). As a consequence (i.e. this function returns true), + // a quit confirmation dialog is shown. + + // TODO: ScummVM currently has a bug / misfeature where some engines provide + // quit confirmation dialogs, some don't; in addition, we have a global confirmation + // dialog (but the user has to explicitly activate that in the config). + // Anyway, this can lead to *two* confirmation dialogs being shown. + // If it wasn't for that, we could simply check for Engine::shouldQuit() here, + // and then invoke EventMan::resetQuit. But currently this would result in + // the user seeing two confirmation dialogs. Bad. + lua_pushbooleancpp(L, false); + + return 1; +} + +static int waitForFocus(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushbooleancpp(L, true); + + return 1; +} + +static int hasFocus(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushbooleancpp(L, true); + + return 1; +} + +static const char *WINDOW_LIBRARY_NAME = "Window"; + +static const luaL_reg WINDOW_FUNCTIONS[] = { + {"IsVisible", isVisible}, + {"SetVisible", setVisible}, + {"GetX", getX}, + {"SetX", setX}, + {"GetY", getY}, + {"SetY", setY}, + {"GetClientX", getClientX}, + {"GetClientY", getClientY}, + {"GetWidth", getWidth}, + {"GetHeight", getHeight}, + {"SetWidth", setWidth}, + {"SetHeight", setHeight}, + {"GetTitle", getTitle}, + {"SetTitle", setTitle}, + {"ProcessMessages", processMessages}, + {"CloseWanted", closeWanted}, + {"WaitForFocus", waitForFocus}, + {"HasFocus", hasFocus}, + {0, 0} +}; + +static int precacheResource(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ResourceManager *pResource = pKernel->getResourceManager(); + BS_ASSERT(pResource); + + lua_pushbooleancpp(L, pResource->precacheResource(luaL_checkstring(L, 1))); + + return 1; +} + +static int forcePrecacheResource(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ResourceManager *pResource = pKernel->getResourceManager(); + BS_ASSERT(pResource); + + lua_pushbooleancpp(L, pResource->precacheResource(luaL_checkstring(L, 1), true)); + + return 1; +} + +static int getMaxMemoryUsage(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ResourceManager *pResource = pKernel->getResourceManager(); + BS_ASSERT(pResource); + + lua_pushnumber(L, pResource->getMaxMemoryUsage()); + + return 1; +} + +static int setMaxMemoryUsage(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ResourceManager *pResource = pKernel->getResourceManager(); + BS_ASSERT(pResource); + + pResource->setMaxMemoryUsage(static_cast<uint>(lua_tonumber(L, 1))); + + return 0; +} + +static int emptyCache(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ResourceManager *pResource = pKernel->getResourceManager(); + BS_ASSERT(pResource); + + pResource->emptyCache(); + + return 0; +} + +static int isLogCacheMiss(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ResourceManager *pResource = pKernel->getResourceManager(); + BS_ASSERT(pResource); + + lua_pushbooleancpp(L, pResource->isLogCacheMiss()); + + return 1; +} + +static int setLogCacheMiss(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ResourceManager *pResource = pKernel->getResourceManager(); + BS_ASSERT(pResource); + + pResource->setLogCacheMiss(lua_tobooleancpp(L, 1)); + + return 0; +} + +static int dumpLockedResources(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ResourceManager *pResource = pKernel->getResourceManager(); + BS_ASSERT(pResource); + + pResource->dumpLockedResources(); + + return 0; +} + +static const char *RESOURCE_LIBRARY_NAME = "Resource"; + +static const luaL_reg RESOURCE_FUNCTIONS[] = { + {"PrecacheResource", precacheResource}, + {"ForcePrecacheResource", forcePrecacheResource}, + {"GetMaxMemoryUsage", getMaxMemoryUsage}, + {"SetMaxMemoryUsage", setMaxMemoryUsage}, + {"EmptyCache", emptyCache}, + {"IsLogCacheMiss", isLogCacheMiss}, + {"SetLogCacheMiss", setLogCacheMiss}, + {"DumpLockedResources", dumpLockedResources}, + {0, 0} +}; + +static int reloadSlots(lua_State *L) { + PersistenceService::getInstance().reloadSlots(); + lua_pushnil(L); + return 1; +} + +static int getSlotCount(lua_State *L) { + lua_pushnumber(L, PersistenceService::getInstance().getSlotCount()); + return 1; +} + +static int isSlotOccupied(lua_State *L) { + lua_pushbooleancpp(L, PersistenceService::getInstance().isSlotOccupied(static_cast<uint>(luaL_checknumber(L, 1)) - 1)); + return 1; +} + +static int getSavegameDirectory(lua_State *L) { + lua_pushstring(L, PersistenceService::getInstance().getSavegameDirectory().c_str()); + return 1; +} + +static int isSavegameCompatible(lua_State *L) { + lua_pushbooleancpp(L, PersistenceService::getInstance().isSavegameCompatible( + static_cast<uint>(luaL_checknumber(L, 1)) - 1)); + return 1; +} + +static int getSavegameDescription(lua_State *L) { + lua_pushstring(L, PersistenceService::getInstance().getSavegameDescription( + static_cast<uint>(luaL_checknumber(L, 1)) - 1).c_str()); + return 1; +} + +static int getSavegameFilename(lua_State *L) { + lua_pushstring(L, PersistenceService::getInstance().getSavegameFilename(static_cast<uint>(luaL_checknumber(L, 1)) - 1).c_str()); + return 1; +} + +static int loadGame(lua_State *L) { + lua_pushbooleancpp(L, PersistenceService::getInstance().loadGame(static_cast<uint>(luaL_checknumber(L, 1)) - 1)); + return 1; +} + +static int saveGame(lua_State *L) { + lua_pushbooleancpp(L, PersistenceService::getInstance().saveGame(static_cast<uint>(luaL_checknumber(L, 1)) - 1, luaL_checkstring(L, 2))); + return 1; +} + +static const char *PERSISTENCE_LIBRARY_NAME = "Persistence"; + +static const luaL_reg PERSISTENCE_FUNCTIONS[] = { + {"ReloadSlots", reloadSlots}, + {"GetSlotCount", getSlotCount}, + {"IsSlotOccupied", isSlotOccupied}, + {"GetSavegameDirectory", getSavegameDirectory}, + {"IsSavegameCompatible", isSavegameCompatible}, + {"GetSavegameDescription", getSavegameDescription}, + {"GetSavegameFilename", getSavegameFilename}, + {"LoadGame", loadGame}, + {"SaveGame", saveGame}, + {0, 0} +}; + +bool Kernel::registerScriptBindings() { + ScriptEngine *pScript = getScript(); + BS_ASSERT(pScript); + lua_State *L = static_cast<lua_State *>(pScript->getScriptObject()); + BS_ASSERT(L); + + if (!LuaBindhelper::addFunctionsToLib(L, KERNEL_LIBRARY_NAME, KERNEL_FUNCTIONS)) return false; + if (!LuaBindhelper::addFunctionsToLib(L, WINDOW_LIBRARY_NAME, WINDOW_FUNCTIONS)) return false; + if (!LuaBindhelper::addFunctionsToLib(L, RESOURCE_LIBRARY_NAME, RESOURCE_FUNCTIONS)) return false; + if (!LuaBindhelper::addFunctionsToLib(L, PERSISTENCE_LIBRARY_NAME, PERSISTENCE_FUNCTIONS)) return false; + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/log.cpp b/engines/sword25/kernel/log.cpp new file mode 100644 index 0000000000..91b0c36ac1 --- /dev/null +++ b/engines/sword25/kernel/log.cpp @@ -0,0 +1,214 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/log.h" +#include "base/version.h" +#include "common/config-manager.h" +#include "common/fs.h" + +namespace Sword25 { + +static const char *BF_LOG_FILENAME = "log.txt"; +static const size_t LOG_BUFFERSIZE = 1024 * 16; + +// Logging will take place only when it's activated +#ifdef BS_ACTIVATE_LOGGING + +Common::WriteStream *BS_Log::_logFile = NULL; +bool BS_Log::_lineBegin = true; +const char *BS_Log::_prefix = NULL; +const char *BS_Log::_file = NULL; +int BS_Log::_line = 0; +bool BS_Log::_autoNewline = false; + +bool BS_Log::createLog() { + // Open the log file + Common::FSNode dataDir(ConfMan.get("path")); + Common::FSNode file = dataDir.getChild(BF_LOG_FILENAME); + + // Open the file for saving + _logFile = file.createWriteStream(); + + if (_logFile) { + // Add a title into the log file + log("Broken Sword 2.5 Engine - Build: %s - %s - VersionID: %s\n", __DATE__, __TIME__, gScummVMFullVersion); + log("-----------------------------------------------------------------------------------------------------\n"); + + return true; + } + + // Log file could not be created + return false; +} + +void BS_Log::closeLog() { + delete _logFile; + _logFile = NULL; +} + +void BS_Log::log(const char *format, ...) { + char message[LOG_BUFFERSIZE]; + + // Create the message + va_list argList; + va_start(argList, format); + vsnprintf(message, sizeof(message), format, argList); + + // Log the message + writeLog(message); + + flushLog(); +} + +void BS_Log::logPrefix(const char *prefix, const char *format, ...) { + char message[LOG_BUFFERSIZE]; + char extFormat[LOG_BUFFERSIZE]; + + // If the issue has ceased at the beginning of a new line, the new issue to begin with the prefix + extFormat[0] = 0; + if (_lineBegin) { + snprintf(extFormat, sizeof(extFormat), "%s: ", prefix); + _lineBegin = false; + } + // Format String pass line by line and each line with the initial prefix + for (;;) { + const char *nextLine = strstr(format, "\n"); + if (!nextLine || *(nextLine + strlen("\n")) == 0) { + Common::strlcat(extFormat, format, sizeof(extFormat)); + if (nextLine) + _lineBegin = true; + break; + } else { + strncat(extFormat, format, (nextLine - format) + strlen("\n")); + Common::strlcat(extFormat, prefix, sizeof(extFormat)); + Common::strlcat(extFormat, ": ", sizeof(extFormat)); + } + + format = nextLine + strlen("\n"); + } + + // Create message + va_list argList; + va_start(argList, format); + vsnprintf(message, sizeof(message), extFormat, argList); + + // Log the message + writeLog(message); + + flushLog(); +} + +void BS_Log::logDecorated(const char *format, ...) { + // Nachricht erzeugen + char message[LOG_BUFFERSIZE]; + va_list argList; + va_start(argList, format); + vsnprintf(message, sizeof(message), format, argList); + + // Zweiten prefix erzeugen, falls gewünscht + char secondaryPrefix[1024]; + if (_file && _line) + snprintf(secondaryPrefix, sizeof(secondaryPrefix), "(file: %s, line: %d) - ", _file, _line); + + // Nachricht zeilenweise ausgeben und an jeden Zeilenanfang das Präfix setzen + char *messageWalker = message; + for (;;) { + char *nextLine = strstr(messageWalker, "\n"); + if (nextLine) { + *nextLine = 0; + if (_lineBegin) { + writeLog(_prefix); + if (_file && _line) + writeLog(secondaryPrefix); + } + writeLog(messageWalker); + writeLog("\n"); + messageWalker = nextLine + sizeof("\n") - 1; + _lineBegin = true; + } else { + if (_lineBegin) { + writeLog(_prefix); + if (_file && _line) + writeLog(secondaryPrefix); + } + writeLog(messageWalker); + _lineBegin = false; + break; + } + } + + // Falls gewünscht, wird ans Ende der Nachricht automatisch ein Newline angehängt. + if (_autoNewline) { + writeLog("\n"); + _lineBegin = true; + } + + // Pseudoparameter zurücksetzen + _prefix = NULL; + _file = 0; + _line = 0; + _autoNewline = false; + + flushLog(); +} + +int BS_Log::writeLog(const char *message) { + if (!_logFile && !createLog()) + return false; + + debugN(0, "%s", message); + + _logFile->writeString(message); + + return true; +} + +void BS_Log::flushLog() { + if (_logFile) + _logFile->flush(); +} + +void (*BS_LogPtr)(const char *, ...) = BS_Log::log; + +void BS_Log_C(const char *message) { + BS_LogPtr(message); +} + +#else + +void BS_Log_C(const char *message) {}; + +#endif + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/log.h b/engines/sword25/kernel/log.h new file mode 100644 index 0000000000..e7c5789a53 --- /dev/null +++ b/engines/sword25/kernel/log.h @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_LOG_H +#define SWORD25_LOG_H + +// Includes +#include "common/array.h" +#include "common/file.h" +#include "sword25/kernel/common.h" + +namespace Sword25 { + +// Logging soll nur stattfinden wenn es aktiviert ist +#ifdef BS_ACTIVATE_LOGGING + +// Logging-Makros +#define BS_LOG BS_Log::setPrefix(BS_LOG_PREFIX ": "), BS_Log::logDecorated +#define BS_LOGLN BS_Log::setPrefix(BS_LOG_PREFIX ": "), BS_Log::setAutoNewline(true), BS_Log::logDecorated +#define BS_LOG_WARNING BS_Log::setPrefix(BS_LOG_PREFIX ": WARNING - "), BS_Log::logDecorated +#define BS_LOG_WARNINGLN BS_Log::setPrefix(BS_LOG_PREFIX ": WARNING - "), BS_Log::setAutoNewline(true), BS_Log::logDecorated +#define BS_LOG_ERROR BS_Log::setPrefix(BS_LOG_PREFIX ": ERROR - "), BS_Log::logDecorated +#define BS_LOG_ERRORLN BS_Log::setPrefix(BS_LOG_PREFIX ": ERROR - "), BS_Log::setAutoNewline(true), BS_Log::logDecorated +#define BS_LOG_EXTERROR BS_Log::setPrefix(BS_LOG_PREFIX ": ERROR "), BS_Log::setFile(__FILE__), BS_Log::setLine(__LINE__), BS_Log::logDecorated +#define BS_LOG_EXTERRORLN BS_Log::setPrefix(BS_LOG_PREFIX ": ERROR "), BS_Log::setFile(__FILE__), BS_Log::setLine(__LINE__), BS_Log::setAutoNewline(true), BS_Log::logDecorated + +// Die Version der Logging-Klasse mit aktiviertem Logging +class BS_Log { +public: + static void clear(); + static void log(const char *format, ...); + static void logPrefix(const char *prefix, const char *format, ...); + static void logDecorated(const char *format, ...); + + static void setPrefix(const char *prefix) { + _prefix = prefix; + } + static void setFile(const char *file) { + _file = file; + } + static void setLine(int line) { + _line = line; + } + static void setAutoNewline(bool autoNewline) { + _autoNewline = autoNewline; + } + + static void closeLog(); + +private: + static Common::WriteStream *_logFile; + static bool _lineBegin; + static const char *_prefix; + static const char *_file; + static int _line; + static bool _autoNewline; + + static bool createLog(); + + static int writeLog(const char *message); + static void flushLog(); +}; + +// Auxiliary function that allows to log C functions (needed for Lua). +#define BS_Log_C error + + +#else + +// Logging-Macros +#define BS_LOG +#define BS_LOGLN +#define BS_LOG_WARNING +#define BS_LOG_WARNINGLN +#define BS_LOG_ERROR +#define BS_LOG_ERRORLN +#define BS_LOG_EXTERROR +#define BS_LOG_EXTERRORLN + +// The version of the logging class with logging disabled +class BS_Log { +public: + // This version implements all the various methods as empty stubs + static void log(const char *text, ...) {}; + static void logPrefix(const char *prefix, const char *format, ...) {}; + static void logDecorated(const char *format, ...) {}; + + static void setPrefix(const char *prefix) {}; + static void setFile(const char *file) {}; + static void setLine(int line) {}; + static void setAutoNewline(bool autoNewline) {}; + + typedef void (*LOG_LISTENER_CALLBACK)(const char *); + static void registerLogListener(LOG_LISTENER_CALLBACK callback) {}; +}; + +#define BS_Log_C error + +#endif + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/objectregistry.h b/engines/sword25/kernel/objectregistry.h new file mode 100644 index 0000000000..3a27ba4942 --- /dev/null +++ b/engines/sword25/kernel/objectregistry.h @@ -0,0 +1,173 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_OBJECTREGISTRY_H +#define SWORD25_OBJECTREGISTRY_H + +#include "common/func.h" +#include "common/hashmap.h" +#include "sword25/kernel/common.h" + +namespace Sword25 { + +template<typename T> +class ObjectRegistry { +public: + ObjectRegistry() : _nextHandle(1) {} + virtual ~ObjectRegistry() {} + + uint registerObject(T *objectPtr) { + // Null-Pointer können nicht registriert werden. + if (objectPtr == 0) { + logErrorLn("Cannot register a null pointer."); + return 0; + } + + // Falls das Objekt bereits registriert wurde, wird eine Warnung ausgeben und das Handle zurückgeben. + uint handle = findHandleByPtr(objectPtr); + if (handle != 0) { + logWarningLn("Tried to register a object that was already registered."); + return handle; + } + // Ansonsten wird das Objekt in beide Maps eingetragen und das neue Handle zurückgeben. + else { + _handle2PtrMap[_nextHandle] = objectPtr; + _ptr2HandleMap[objectPtr] = _nextHandle; + + return _nextHandle++; + } + } + + uint registerObject(T *objectPtr, uint handle) { + // Null-Pointer und Null-Handle können nicht registriert werden. + if (objectPtr == 0 || handle == 0) { + logErrorLn("Cannot register a null pointer or a null handle."); + return 0; + } + + // Falls das Objekt bereits registriert wurde, wird ein Fehler ausgegeben und 0 zurückgeben. + uint handleTest = findHandleByPtr(objectPtr); + if (handleTest != 0) { + logErrorLn("Tried to register a object that was already registered."); + return 0; + } + // Falls das Handle bereits vergeben ist, wird ein Fehler ausgegeben und 0 zurückgegeben. + else if (findPtrByHandle(handle) != 0) { + logErrorLn("Tried to register a handle that is already taken."); + return 0; + } + // Ansonsten wird das Objekt in beide Maps eingetragen und das gewünschte Handle zurückgeben. + else { + _handle2PtrMap[handle] = objectPtr; + _ptr2HandleMap[objectPtr] = handle; + + // Falls das vergebene Handle größer oder gleich dem nächsten automatische vergebenen Handle ist, wird das nächste automatisch + // vergebene Handle erhöht. + if (handle >= _nextHandle) + _nextHandle = handle + 1; + + return handle; + } + } + + void deregisterObject(T *objectPtr) { + uint handle = findHandleByPtr(objectPtr); + + if (handle != 0) { + // Registriertes Objekt aus beiden Maps entfernen. + _handle2PtrMap.erase(findHandleByPtr(objectPtr)); + _ptr2HandleMap.erase(objectPtr); + } else { + logWarningLn("Tried to remove a object that was not registered."); + } + } + + T *resolveHandle(uint handle) { + // Zum Handle gehöriges Objekt in der Hash-Map finden. + T *objectPtr = findPtrByHandle(handle); + + // Pointer zurückgeben. Im Fehlerfall ist dieser 0. + return objectPtr; + } + + uint resolvePtr(T *objectPtr) { + // Zum Pointer gehöriges Handle in der Hash-Map finden. + uint handle = findHandleByPtr(objectPtr); + + // Handle zurückgeben. Im Fehlerfall ist dieses 0. + return handle; + } + +protected: + struct ClassPointer_EqualTo { + bool operator()(const T *x, const T *y) const { + return x == y; + } + }; + struct ClassPointer_Hash { + uint operator()(const T *x) const { + return *(uint *)&x; + } + }; + + typedef Common::HashMap<uint, T *> HANDLE2PTR_MAP; + typedef Common::HashMap<T *, uint, ClassPointer_Hash, ClassPointer_EqualTo> PTR2HANDLE_MAP; + + HANDLE2PTR_MAP _handle2PtrMap; + PTR2HANDLE_MAP _ptr2HandleMap; + uint _nextHandle; + + T *findPtrByHandle(uint handle) { + // Zum Handle gehörigen Pointer finden. + typename HANDLE2PTR_MAP::const_iterator it = _handle2PtrMap.find(handle); + + // Pointer zurückgeben, oder, falls keiner gefunden wurde, 0 zurückgeben. + return (it != _handle2PtrMap.end()) ? it->_value : 0; + } + + uint findHandleByPtr(T *objectPtr) { + // Zum Pointer gehöriges Handle finden. + typename PTR2HANDLE_MAP::const_iterator it = _ptr2HandleMap.find(objectPtr); + + // Handle zurückgeben, oder, falls keines gefunden wurde, 0 zurückgeben. + return (it != _ptr2HandleMap.end()) ? it->_value : 0; + } + + virtual void logErrorLn(const char *message) const = 0; + virtual void logWarningLn(const char *message) const = 0; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/outputpersistenceblock.cpp b/engines/sword25/kernel/outputpersistenceblock.cpp new file mode 100644 index 0000000000..b1c3233b59 --- /dev/null +++ b/engines/sword25/kernel/outputpersistenceblock.cpp @@ -0,0 +1,101 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "OUTPUTPERSISTENCEBLOCK" + +#include "sword25/kernel/outputpersistenceblock.h" + +namespace { +const uint INITIAL_BUFFER_SIZE = 1024 * 64; +} + +namespace Sword25 { + +OutputPersistenceBlock::OutputPersistenceBlock() { + _data.reserve(INITIAL_BUFFER_SIZE); +} + +void OutputPersistenceBlock::write(signed int value) { + writeMarker(SINT_MARKER); + value = convertEndianessFromSystemToStorage(value); + rawWrite(&value, sizeof(value)); +} + +void OutputPersistenceBlock::write(uint value) { + writeMarker(UINT_MARKER); + value = convertEndianessFromSystemToStorage(value); + rawWrite(&value, sizeof(value)); +} + +void OutputPersistenceBlock::write(float value) { + writeMarker(FLOAT_MARKER); + value = convertEndianessFromSystemToStorage(value); + rawWrite(&value, sizeof(value)); +} + +void OutputPersistenceBlock::write(bool value) { + writeMarker(BOOL_MARKER); + + uint uintBool = value ? 1 : 0; + uintBool = convertEndianessFromSystemToStorage(uintBool); + rawWrite(&uintBool, sizeof(uintBool)); +} + +void OutputPersistenceBlock::writeString(const Common::String &string) { + writeMarker(STRING_MARKER); + + write(string.size()); + rawWrite(string.c_str(), string.size()); +} + +void OutputPersistenceBlock::writeByteArray(Common::Array<byte> &value) { + writeMarker(BLOCK_MARKER); + + write((uint)value.size()); + rawWrite(&value[0], value.size()); +} + +void OutputPersistenceBlock::writeMarker(byte marker) { + _data.push_back(marker); +} + +void OutputPersistenceBlock::rawWrite(const void *dataPtr, size_t size) { + if (size > 0) { + uint oldSize = _data.size(); + _data.resize(oldSize + size); + memcpy(&_data[oldSize], dataPtr, size); + } +} + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/outputpersistenceblock.h b/engines/sword25/kernel/outputpersistenceblock.h new file mode 100644 index 0000000000..71dbe68a52 --- /dev/null +++ b/engines/sword25/kernel/outputpersistenceblock.h @@ -0,0 +1,70 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_OUTPUTPERSISTENCEBLOCK_H +#define SWORD25_OUTPUTPERSISTENCEBLOCK_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistenceblock.h" + +namespace Sword25 { + +class OutputPersistenceBlock : public PersistenceBlock { +public: + OutputPersistenceBlock(); + + void write(signed int value); + void write(uint value); + void write(float value); + void write(bool value); + void writeString(const Common::String &string); + void writeByteArray(Common::Array<byte> &value); + + const void *getData() const { + return &_data[0]; + } + uint getDataSize() const { + return _data.size(); + } + +private: + void writeMarker(byte marker); + void rawWrite(const void *dataPtr, size_t size); + + Common::Array<byte> _data; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/persistable.h b/engines/sword25/kernel/persistable.h new file mode 100644 index 0000000000..25cf70fda0 --- /dev/null +++ b/engines/sword25/kernel/persistable.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_PERSISTABLE_H +#define SWORD25_PERSISTABLE_H + +namespace Sword25 { + +class OutputPersistenceBlock; +class InputPersistenceBlock; + +class Persistable { +public: + virtual ~Persistable() {} + + virtual bool persist(OutputPersistenceBlock &writer) = 0; + virtual bool unpersist(InputPersistenceBlock &reader) = 0; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/persistenceblock.h b/engines/sword25/kernel/persistenceblock.h new file mode 100644 index 0000000000..4a64eff11b --- /dev/null +++ b/engines/sword25/kernel/persistenceblock.h @@ -0,0 +1,123 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_PERSISTENCEBLOCK_H +#define SWORD25_PERSISTENCEBLOCK_H + +#include "sword25/kernel/common.h" + +namespace Sword25 { + +class PersistenceBlock { +public: + static uint getSInt32Size() { + return sizeof(signed int) + sizeof(byte); + } + static uint getUInt32Size() { + return sizeof(uint) + sizeof(byte); + } + static uint getFloat32Size() { + return sizeof(float) + sizeof(byte); + } + static uint getBoolSize() { + return sizeof(byte) + sizeof(byte); + } + static uint getStringSize(const Common::String &string) { + return static_cast<uint>(sizeof(uint) + string.size() + sizeof(byte)); + } + +protected: + enum { + SINT_MARKER, + UINT_MARKER, + FLOAT_MARKER, + STRING_MARKER, + BOOL_MARKER, + BLOCK_MARKER + }; + + // ----------------------------------------------------------------------------- + // Endianess Conversions + // ----------------------------------------------------------------------------- + // + // Everything is stored in Little Endian + // Big Endian Systems will need to be byte swapped during both saving and reading of saved values + // + + template<typename T> + static T convertEndianessFromSystemToStorage(T value) { + if (isBigEndian()) + reverseByteOrder(&value); + return value; + } + + template<typename T> + static T convertEndianessFromStorageToSystem(T value) { + if (isBigEndian()) + reverseByteOrder(&value); + return value; + } + +private: + static bool isBigEndian() { + uint dummy = 1; + byte *dummyPtr = reinterpret_cast<byte *>(&dummy); + return dummyPtr[0] == 0; + } + + template<typename T> + static void swap(T &one, T &two) { + T temp = one; + one = two; + two = temp; + } + + static void reverseByteOrder(void *ptr) { + // Reverses the byte order of the 32-bit word pointed to by Ptr + byte *charPtr = static_cast<byte *>(ptr); + swap(charPtr[0], charPtr[3]); + swap(charPtr[1], charPtr[2]); + } +}; + +#define CTASSERT(ex) typedef char ctassert_type[(ex) ? 1 : -1] +CTASSERT(sizeof(byte) == 1); +CTASSERT(sizeof(signed int) == 4); +CTASSERT(sizeof(uint) == 4); +CTASSERT(sizeof(float) == 4); +#undef CTASSERT + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/persistenceservice.cpp b/engines/sword25/kernel/persistenceservice.cpp new file mode 100644 index 0000000000..564f031cf3 --- /dev/null +++ b/engines/sword25/kernel/persistenceservice.cpp @@ -0,0 +1,420 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "common/fs.h" +#include "common/savefile.h" +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/persistenceservice.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/filesystemutil.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/sfx/soundengine.h" +#include "sword25/input/inputengine.h" +#include "sword25/math/regionregistry.h" +#include "sword25/script/script.h" +#include <zlib.h> + +#define BS_LOG_PREFIX "PERSISTENCESERVICE" + +namespace Sword25 { + +//static const char *SAVEGAME_EXTENSION = ".b25s"; +static const char *SAVEGAME_DIRECTORY = "saves"; +static const char *FILE_MARKER = "BS25SAVEGAME"; +static const uint SLOT_COUNT = 18; +static const uint FILE_COPY_BUFFER_SIZE = 1024 * 10; +static const char *VERSIONID = "SCUMMVM1"; + +#define MAX_SAVEGAME_SIZE 100 + +char gameTarget[MAX_SAVEGAME_SIZE]; + +void setGameTarget(const char *target) { + strncpy(gameTarget, target, MAX_SAVEGAME_SIZE); +} + +static Common::String generateSavegameFilename(uint slotID) { + char buffer[MAX_SAVEGAME_SIZE]; + snprintf(buffer, MAX_SAVEGAME_SIZE, "%s.%.3d", gameTarget, slotID); + return Common::String(buffer); +} + +static Common::String formatTimestamp(TimeDate time) { + // In the original BS2.5 engine, this used a local object to show the date/time as as a string. + // For now in ScummVM it's being hardcoded to 'dd-MON-yyyy hh:mm:ss' + Common::String monthList[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + char buffer[100]; + snprintf(buffer, 100, "%.2d-%s-%.4d %.2d:%.2d:%.2d", + time.tm_mday, monthList[time.tm_mon].c_str(), 1900 + time.tm_year, + time.tm_hour, time.tm_min, time.tm_sec + ); + + return Common::String(buffer); +} + +static Common::String loadString(Common::InSaveFile *in, uint maxSize = 999) { + Common::String result; + + char ch = (char)in->readByte(); + while (ch != '\0') { + result += ch; + if (result.size() >= maxSize) + break; + ch = (char)in->readByte(); + } + + return result; +} + +struct SavegameInformation { + bool isOccupied; + bool isCompatible; + Common::String description; + Common::String filename; + uint gamedataLength; + uint gamedataOffset; + uint gamedataUncompressedLength; + + SavegameInformation() { + clear(); + } + + void clear() { + isOccupied = false; + isCompatible = false; + description = ""; + filename = ""; + gamedataLength = 0; + gamedataOffset = 0; + gamedataUncompressedLength = 0; + } +}; + +struct PersistenceService::Impl { + SavegameInformation _savegameInformations[SLOT_COUNT]; + + Impl() { + reloadSlots(); + } + + void reloadSlots() { + // Über alle Spielstanddateien iterieren und deren Infos einlesen. + for (uint i = 0; i < SLOT_COUNT; ++i) { + readSlotSavegameInformation(i); + } + } + + void readSlotSavegameInformation(uint slotID) { + // Aktuelle Slotinformationen in den Ausgangszustand versetzen, er wird im Folgenden neu gefüllt. + SavegameInformation &curSavegameInfo = _savegameInformations[slotID]; + curSavegameInfo.clear(); + + // Den Dateinamen für den Spielstand des Slots generieren. + Common::String filename = generateSavegameFilename(slotID); + + // Try to open the savegame for loading + Common::SaveFileManager *sfm = g_system->getSavefileManager(); + Common::InSaveFile *file = sfm->openForLoading(filename); + + if (file) { + // Read in the header + Common::String storedMarker = loadString(file); + Common::String storedVersionID = loadString(file); + Common::String gameDescription = loadString(file); + Common::String gameDataLength = loadString(file); + curSavegameInfo.gamedataLength = atoi(gameDataLength.c_str()); + Common::String gamedataUncompressedLength = loadString(file); + curSavegameInfo.gamedataUncompressedLength = atoi(gamedataUncompressedLength.c_str()); + + // If the header can be read in and is detected to be valid, we will have a valid file + if (storedMarker == FILE_MARKER) { + // Der Slot wird als belegt markiert. + curSavegameInfo.isOccupied = true; + // Speichern, ob der Spielstand kompatibel mit der aktuellen Engine-Version ist. + curSavegameInfo.isCompatible = (storedVersionID == Common::String(VERSIONID)); + // Dateinamen des Spielstandes speichern. + curSavegameInfo.filename = generateSavegameFilename(slotID); + // Die Beschreibung des Spielstandes besteht aus einer textuellen Darstellung des Änderungsdatums der Spielstanddatei. + curSavegameInfo.description = gameDescription; + // Den Offset zu den gespeicherten Spieldaten innerhalb der Datei speichern. + // Dieses entspricht der aktuellen Position, da nach der letzten Headerinformation noch ein Leerzeichen als trenner folgt. + curSavegameInfo.gamedataOffset = static_cast<uint>(file->pos()); + } + + delete file; + } + } +}; + +PersistenceService &PersistenceService::getInstance() { + static PersistenceService instance; + return instance; +} + +PersistenceService::PersistenceService() : _impl(new Impl) { +} + +PersistenceService::~PersistenceService() { + delete _impl; +} + +void PersistenceService::reloadSlots() { + _impl->reloadSlots(); +} + +uint PersistenceService::getSlotCount() { + return SLOT_COUNT; +} + +Common::String PersistenceService::getSavegameDirectory() { + Common::FSNode node(FileSystemUtil::getUserdataDirectory()); + Common::FSNode childNode = node.getChild(SAVEGAME_DIRECTORY); + + // Try and return the path using the savegame subfolder. But if doesn't exist, fall back on the data directory + if (childNode.exists()) + return childNode.getPath(); + + return node.getPath(); +} + +namespace { +bool checkslotID(uint slotID) { + // Überprüfen, ob die Slot-ID zulässig ist. + if (slotID >= SLOT_COUNT) { + BS_LOG_ERRORLN("Tried to access an invalid slot (%d). Only slot ids from 0 to %d are allowed.", slotID, SLOT_COUNT - 1); + return false; + } else { + return true; + } +} +} + +bool PersistenceService::isSlotOccupied(uint slotID) { + if (!checkslotID(slotID)) + return false; + return _impl->_savegameInformations[slotID].isOccupied; +} + +bool PersistenceService::isSavegameCompatible(uint slotID) { + if (!checkslotID(slotID)) + return false; + return _impl->_savegameInformations[slotID].isCompatible; +} + +Common::String &PersistenceService::getSavegameDescription(uint slotID) { + static Common::String emptyString; + if (!checkslotID(slotID)) + return emptyString; + return _impl->_savegameInformations[slotID].description; +} + +Common::String &PersistenceService::getSavegameFilename(uint slotID) { + static Common::String emptyString; + if (!checkslotID(slotID)) + return emptyString; + return _impl->_savegameInformations[slotID].filename; +} + +bool PersistenceService::saveGame(uint slotID, const Common::String &screenshotFilename) { + // FIXME: This code is a hack which bypasses the savefile API, + // and should eventually be removed. + + // Überprüfen, ob die Slot-ID zulässig ist. + if (slotID >= SLOT_COUNT) { + BS_LOG_ERRORLN("Tried to save to an invalid slot (%d). Only slot ids form 0 to %d are allowed.", slotID, SLOT_COUNT - 1); + return false; + } + + // Dateinamen erzeugen. + Common::String filename = generateSavegameFilename(slotID); + + // Spielstanddatei öffnen und die Headerdaten schreiben. + Common::SaveFileManager *sfm = g_system->getSavefileManager(); + Common::OutSaveFile *file = sfm->openForSaving(filename); + + file->writeString(FILE_MARKER); + file->writeByte(0); + file->writeString(VERSIONID); + file->writeByte(0); + + TimeDate dt; + g_system->getTimeAndDate(dt); + file->writeString(formatTimestamp(dt)); + file->writeByte(0); + + if (file->err()) { + error("Unable to write header data to savegame file \"%s\".", filename.c_str()); + } + + // Alle notwendigen Module persistieren. + OutputPersistenceBlock writer; + bool success = true; + success &= Kernel::getInstance()->getScript()->persist(writer); + success &= RegionRegistry::instance().persist(writer); + success &= Kernel::getInstance()->getGfx()->persist(writer); + success &= Kernel::getInstance()->getSfx()->persist(writer); + success &= Kernel::getInstance()->getInput()->persist(writer); + if (!success) { + error("Unable to persist modules for savegame file \"%s\".", filename.c_str()); + } + + // Daten komprimieren. + uLongf compressedLength = writer.getDataSize() + (writer.getDataSize() + 500) / 1000 + 12; + Bytef *compressionBuffer = new Bytef[compressedLength]; + + if (compress2(&compressionBuffer[0], &compressedLength, reinterpret_cast<const Bytef *>(writer.getData()), writer.getDataSize(), 6) != Z_OK) { + error("Unable to compress savegame data in savegame file \"%s\".", filename.c_str()); + } + + // Länge der komprimierten Daten und der unkomprimierten Daten in die Datei schreiben. + char sBuffer[10]; + snprintf(sBuffer, 10, "%ld", compressedLength); + file->writeString(sBuffer); + file->writeByte(0); + snprintf(sBuffer, 10, "%u", writer.getDataSize()); + file->writeString(sBuffer); + file->writeByte(0); + + // Komprimierte Daten in die Datei schreiben. + file->write(reinterpret_cast<char *>(&compressionBuffer[0]), compressedLength); + if (file->err()) { + error("Unable to write game data to savegame file \"%s\".", filename.c_str()); + } + + // Get the screenshot + Common::MemoryReadStream *thumbnail = Kernel::getInstance()->getGfx()->getThumbnail(); + + if (thumbnail) { + byte *buffer = new Byte[FILE_COPY_BUFFER_SIZE]; + while (!thumbnail->eos()) { + int bytesRead = thumbnail->read(&buffer[0], FILE_COPY_BUFFER_SIZE); + file->write(&buffer[0], bytesRead); + } + + delete[] buffer; + } else { + BS_LOG_WARNINGLN("The screenshot file \"%s\" does not exist. Savegame is written without a screenshot.", filename.c_str()); + } + + file->finalize(); + delete file; + delete[] compressionBuffer; + + // Savegameinformationen für diesen Slot aktualisieren. + _impl->readSlotSavegameInformation(slotID); + + // Erfolg signalisieren. + return true; +} + +bool PersistenceService::loadGame(uint slotID) { + Common::SaveFileManager *sfm = g_system->getSavefileManager(); + Common::InSaveFile *file; + + // Überprüfen, ob die Slot-ID zulässig ist. + if (slotID >= SLOT_COUNT) { + BS_LOG_ERRORLN("Tried to load from an invalid slot (%d). Only slot ids form 0 to %d are allowed.", slotID, SLOT_COUNT - 1); + return false; + } + + SavegameInformation &curSavegameInfo = _impl->_savegameInformations[slotID]; + + // Überprüfen, ob der Slot belegt ist. + if (!curSavegameInfo.isOccupied) { + BS_LOG_ERRORLN("Tried to load from an empty slot (%d).", slotID); + return false; + } + + // Überprüfen, ob der Spielstand im angegebenen Slot mit der aktuellen Engine-Version kompatibel ist. + // Im Debug-Modus wird dieser Test übersprungen. Für das Testen ist es hinderlich auf die Einhaltung dieser strengen Bedingung zu bestehen, + // da sich die Versions-ID bei jeder Codeänderung mitändert. +#ifndef DEBUG + if (!curSavegameInfo.isCompatible) { + BS_LOG_ERRORLN("Tried to load a savegame (%d) that is not compatible with this engine version.", slotID); + return false; + } +#endif + + byte *compressedDataBuffer = new byte[curSavegameInfo.gamedataLength]; + byte *uncompressedDataBuffer = new Bytef[curSavegameInfo.gamedataUncompressedLength]; + + file = sfm->openForLoading(generateSavegameFilename(slotID)); + + file->seek(curSavegameInfo.gamedataOffset); + file->read(reinterpret_cast<char *>(&compressedDataBuffer[0]), curSavegameInfo.gamedataLength); + if (file->err()) { + BS_LOG_ERRORLN("Unable to load the gamedata from the savegame file \"%s\".", curSavegameInfo.filename.c_str()); + delete[] compressedDataBuffer; + delete[] uncompressedDataBuffer; + return false; + } + + // Spieldaten dekomprimieren. + uLongf uncompressedBufferSize = curSavegameInfo.gamedataUncompressedLength; + if (uncompress(reinterpret_cast<Bytef *>(&uncompressedDataBuffer[0]), &uncompressedBufferSize, + reinterpret_cast<Bytef *>(&compressedDataBuffer[0]), curSavegameInfo.gamedataLength) != Z_OK) { + BS_LOG_ERRORLN("Unable to decompress the gamedata from savegame file \"%s\".", curSavegameInfo.filename.c_str()); + delete[] uncompressedDataBuffer; + delete[] compressedDataBuffer; + delete file; + return false; + } + + InputPersistenceBlock reader(&uncompressedDataBuffer[0], curSavegameInfo.gamedataUncompressedLength); + + // Einzelne Engine-Module depersistieren. + bool success = true; + success &= Kernel::getInstance()->getScript()->unpersist(reader); + // Muss unbedingt nach Script passieren. Da sonst die bereits wiederhergestellten Regions per Garbage-Collection gekillt werden. + success &= RegionRegistry::instance().unpersist(reader); + success &= Kernel::getInstance()->getGfx()->unpersist(reader); + success &= Kernel::getInstance()->getSfx()->unpersist(reader); + success &= Kernel::getInstance()->getInput()->unpersist(reader); + + delete[] compressedDataBuffer; + delete[] uncompressedDataBuffer; + delete file; + + if (!success) { + BS_LOG_ERRORLN("Unable to unpersist the gamedata from savegame file \"%s\".", curSavegameInfo.filename.c_str()); + return false; + } + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/persistenceservice.h b/engines/sword25/kernel/persistenceservice.h new file mode 100644 index 0000000000..0db109d1b0 --- /dev/null +++ b/engines/sword25/kernel/persistenceservice.h @@ -0,0 +1,78 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_PERSISTENCESERVICE_H +#define SWORD25_PERSISTENCESERVICE_H + +#include "sword25/kernel/common.h" + +namespace Sword25 { + +class PersistenceService { +public: + PersistenceService(); + virtual ~PersistenceService(); + + // ----------------------------------------------------------------------------- + // Singleton Method + // ----------------------------------------------------------------------------- + + static PersistenceService &getInstance(); + + // ----------------------------------------------------------------------------- + // Interface + // ----------------------------------------------------------------------------- + + static uint getSlotCount(); + static Common::String getSavegameDirectory(); + + void reloadSlots(); + bool isSlotOccupied(uint slotID); + bool isSavegameCompatible(uint slotID); + Common::String &getSavegameDescription(uint slotID); + Common::String &getSavegameFilename(uint slotID); + + bool saveGame(uint slotID, const Common::String &screenshotFilename); + bool loadGame(uint slotID); + +private: + struct Impl; + Impl *_impl; +}; + +void setGameTarget(const char *target); + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/resmanager.cpp b/engines/sword25/kernel/resmanager.cpp new file mode 100644 index 0000000000..1979e6e6c6 --- /dev/null +++ b/engines/sword25/kernel/resmanager.cpp @@ -0,0 +1,303 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/resmanager.h" + +#include "sword25/kernel/resource.h" +#include "sword25/kernel/resservice.h" +#include "sword25/package/packagemanager.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "RESOURCEMANAGER" + +ResourceManager::~ResourceManager() { + // Clear all unlocked resources + emptyCache(); + + // All remaining resources are not released, so print warnings and release + Common::List<Resource *>::iterator iter = _resources.begin(); + for (; iter != _resources.end(); ++iter) { + BS_LOG_WARNINGLN("Resource \"%s\" was not released.", (*iter)->getFileName().c_str()); + + // Set the lock count to zero + while ((*iter)->getLockCount() > 0) { + (*iter)->release(); + }; + + // Delete the resource + delete(*iter); + } +} + +/** + * Registers a RegisterResourceService. This method is the constructor of + * BS_ResourceService, and thus helps all resource services in the ResourceManager list + * @param pService Which service + */ +bool ResourceManager::registerResourceService(ResourceService *pService) { + if (!pService) { + BS_LOG_ERRORLN("Can't register NULL resource service."); + return false; + } + + _resourceServices.push_back(pService); + + return true; +} + +/** + * Deletes resources as necessary until the specified memory limit is not being exceeded. + */ +void ResourceManager::deleteResourcesIfNecessary() { + // If enough memory is available, or no resources are loaded, then the function can immediately end + if (_kernelPtr->getUsedMemory() < _maxMemoryUsage || _resources.empty()) + return; + + // Keep deleting resources until the memory usage of the process falls below the set maximum limit. + // The list is processed backwards in order to first release those resources who have been + // not been accessed for the longest + Common::List<Resource *>::iterator iter = _resources.end(); + do { + --iter; + + // The resource may be released only if it isn't locked + if ((*iter)->getLockCount() == 0) + iter = deleteResource(*iter); + } while (iter != _resources.begin() && _kernelPtr->getUsedMemory() > _maxMemoryUsage); +} + +/** + * Releases all resources that are not locked. + */ +void ResourceManager::emptyCache() { + // Scan through the resource list + Common::List<Resource *>::iterator iter = _resources.begin(); + while (iter != _resources.end()) { + if ((*iter)->getLockCount() == 0) { + // Delete the resource + iter = deleteResource(*iter); + } else + ++iter; + } +} + +/** + * Returns a requested resource. If any error occurs, returns NULL + * @param FileName Filename of resource + */ +Resource *ResourceManager::requestResource(const Common::String &fileName) { + // Get the absolute path to the file + Common::String uniqueFileName = getUniqueFileName(fileName); + if (uniqueFileName.empty()) + return NULL; + + // Determine whether the resource is already loaded + // If the resource is found, it will be placed at the head of the resource list and returned + { + Resource *pResource = getResource(uniqueFileName); + if (pResource) { + moveToFront(pResource); + (pResource)->addReference(); + return pResource; + } + } + + // The resource was not found, therefore, must not be loaded yet + if (_logCacheMiss) + BS_LOG_WARNINGLN("\"%s\" was not precached.", uniqueFileName.c_str()); + + Resource *pResource = loadResource(uniqueFileName); + if (pResource) { + pResource->addReference(); + return pResource; + } + + return NULL; +} + +/** + * Loads a resource into the cache + * @param FileName The filename of the resource to be cached + * @param ForceReload Indicates whether the file should be reloaded if it's already in the cache. + * This is useful for files that may have changed in the interim + */ +bool ResourceManager::precacheResource(const Common::String &fileName, bool forceReload) { + // Get the absolute path to the file + Common::String uniqueFileName = getUniqueFileName(fileName); + if (uniqueFileName.empty()) + return false; + + Resource *resourcePtr = getResource(uniqueFileName); + + if (forceReload && resourcePtr) { + if (resourcePtr->getLockCount()) { + BS_LOG_ERRORLN("Could not force precaching of \"%s\". The resource is locked.", fileName.c_str()); + return false; + } else { + deleteResource(resourcePtr); + resourcePtr = 0; + } + } + + if (!resourcePtr && loadResource(uniqueFileName) == NULL) { + BS_LOG_ERRORLN("Could not precache \"%s\",", fileName.c_str()); + return false; + } + + return true; +} + +/** + * Moves a resource to the top of the resource list + * @param pResource The resource + */ +void ResourceManager::moveToFront(Resource *pResource) { + // Erase the resource from it's current position + _resources.erase(pResource->_iterator); + // Re-add the resource at the front of the list + _resources.push_front(pResource); + // Reset the resource iterator to the repositioned item + pResource->_iterator = _resources.begin(); +} + +/** + * Loads a resource and updates the m_UsedMemory total + * + * The resource must not already be loaded + * @param FileName The unique filename of the resource to be loaded + */ +Resource *ResourceManager::loadResource(const Common::String &fileName) { + // ResourceService finden, der die Resource laden kann. + for (uint i = 0; i < _resourceServices.size(); ++i) { + if (_resourceServices[i]->canLoadResource(fileName)) { + // If more memory is desired, memory must be released + deleteResourcesIfNecessary(); + + // Load the resource + Resource *pResource = _resourceServices[i]->loadResource(fileName); + if (!pResource) { + BS_LOG_ERRORLN("Responsible service could not load resource \"%s\".", fileName.c_str()); + return NULL; + } + + // Add the resource to the front of the list + _resources.push_front(pResource); + pResource->_iterator = _resources.begin(); + + // Also store the resource in the hash table for quick lookup + _resourceHashMap[pResource->getFileName()] = pResource; + + return pResource; + } + } + + BS_LOG_ERRORLN("Could not find a service that can load \"%s\".", fileName.c_str()); + return NULL; +} + +/** + * Returns the full path of a given resource filename. + * It will return an empty string if a path could not be created. +*/ +Common::String ResourceManager::getUniqueFileName(const Common::String &fileName) const { + // Get a pointer to the package manager + PackageManager *pPackage = (PackageManager *)_kernelPtr->getPackage(); + if (!pPackage) { + BS_LOG_ERRORLN("Could not get package manager."); + return Common::String(); + } + + // Absoluten Pfad der Datei bekommen und somit die Eindeutigkeit des Dateinamens sicherstellen + Common::String uniquefileName = pPackage->getAbsolutePath(fileName); + if (uniquefileName.empty()) + BS_LOG_ERRORLN("Could not create absolute file name for \"%s\".", fileName.c_str()); + + return uniquefileName; +} + +/** + * Deletes a resource, removes it from the lists, and updates m_UsedMemory + */ +Common::List<Resource *>::iterator ResourceManager::deleteResource(Resource *pResource) { + // Remove the resource from the hash table + _resourceHashMap.erase(pResource->_fileName); + + // Delete the resource from the resource list + Common::List<Resource *>::iterator result = _resources.erase(pResource->_iterator); + + // Delete the resource + delete pResource; + + // Return the iterator + return result; +} + +/** + * Returns a pointer to a loaded resource. If any error occurs, NULL will be returned. + * @param UniquefileName The absolute path and filename + */ +Resource *ResourceManager::getResource(const Common::String &uniquefileName) const { + // Determine whether the resource is already loaded + ResMap::iterator it = _resourceHashMap.find(uniquefileName); + if (it != _resourceHashMap.end()) + return it->_value; + + // Resource was not found, i.e. has not yet been loaded. + return NULL; +} + +/** + * Writes the names of all currently locked resources to the log file + */ +void ResourceManager::dumpLockedResources() { + for (Common::List<Resource *>::iterator iter = _resources.begin(); iter != _resources.end(); ++iter) { + if ((*iter)->getLockCount() > 0) { + BS_LOGLN("%s", (*iter)->getFileName().c_str()); + } + } +} + +/** + * Specifies the maximum amount of memory the engine is allowed to use. + * If this value is exceeded, resources will be unloaded to make room. This value is meant + * as a guideline, and not as a fixed boundary. It is not guaranteed not to be exceeded; + * the whole game engine may still use more memory than any amount specified. + */ +void ResourceManager::setMaxMemoryUsage(uint maxMemoryUsage) { + _maxMemoryUsage = maxMemoryUsage; + deleteResourcesIfNecessary(); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/resmanager.h b/engines/sword25/kernel/resmanager.h new file mode 100644 index 0000000000..613bb3a3a2 --- /dev/null +++ b/engines/sword25/kernel/resmanager.h @@ -0,0 +1,175 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_RESOURCEMANAGER_H +#define SWORD25_RESOURCEMANAGER_H + +#include "common/list.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +#include "sword25/kernel/common.h" + +namespace Sword25 { + +class ResourceService; +class Resource; +class Kernel; + +class ResourceManager { + friend class Kernel; + +public: + /** + * Returns a requested resource. If any error occurs, returns NULL + * @param FileName Filename of resource + */ + Resource *requestResource(const Common::String &fileName); + + /** + * Loads a resource into the cache + * @param FileName The filename of the resource to be cached + * @param ForceReload Indicates whether the file should be reloaded if it's already in the cache. + * This is useful for files that may have changed in the interim + */ + bool precacheResource(const Common::String &fileName, bool forceReload = false); + + /** + * Registers a RegisterResourceService. This method is the constructor of + * BS_ResourceService, and thus helps all resource services in the ResourceManager list + * @param pService Which service + */ + bool registerResourceService(ResourceService *pService); + + /** + * Releases all resources that are not locked. + **/ + void emptyCache(); + + /** + * Returns the maximum memory the kernel has used + */ + int getMaxMemoryUsage() const { + return _maxMemoryUsage; + } + + /** + * Specifies the maximum amount of memory the engine is allowed to use. + * If this value is exceeded, resources will be unloaded to make room. This value is meant + * as a guideline, and not as a fixed boundary. It is not guaranteed not to be exceeded; + * the whole game engine may still use more memory than any amount specified. + */ + void setMaxMemoryUsage(uint maxMemoryUsage); + + /** + * Specifies whether a warning is written to the log when a cache miss occurs. + * THe default value is "false". + */ + bool isLogCacheMiss() const { + return _logCacheMiss; + } + + /** + * Sets whether warnings are written to the log if a cache miss occurs. + * @param Flag If "true", then future warnings will be logged + */ + void setLogCacheMiss(bool flag) { + _logCacheMiss = flag; + } + + /** + * Writes the names of all currently locked resources to the log file + */ + void dumpLockedResources(); + +private: + /** + * Creates a new resource manager + * Only the BS_Kernel class can generate copies this class. Thus, the constructor is private + */ + ResourceManager(Kernel *pKernel) : + _kernelPtr(pKernel), + _maxMemoryUsage(100000000), + _logCacheMiss(false) + {} + virtual ~ResourceManager(); + + /** + * Moves a resource to the top of the resource list + * @param pResource The resource + */ + void moveToFront(Resource *pResource); + + /** + * Loads a resource and updates the m_UsedMemory total + * + * The resource must not already be loaded + * @param FileName The unique filename of the resource to be loaded + */ + Resource *loadResource(const Common::String &fileName); + + /** + * Returns the full path of a given resource filename. + * It will return an empty string if a path could not be created. + */ + Common::String getUniqueFileName(const Common::String &fileName) const; + + /** + * Deletes a resource, removes it from the lists, and updates m_UsedMemory + */ + Common::List<Resource *>::iterator deleteResource(Resource *pResource); + + /** + * Returns a pointer to a loaded resource. If any error occurs, NULL will be returned. + * @param UniqueFileName The absolute path and filename + */ + Resource *getResource(const Common::String &uniqueFileName) const; + + /** + * Deletes resources as necessary until the specified memory limit is not being exceeded. + */ + void deleteResourcesIfNecessary(); + + Kernel *_kernelPtr; + uint _maxMemoryUsage; + Common::Array<ResourceService *> _resourceServices; + Common::List<Resource *> _resources; + typedef Common::HashMap<Common::String, Resource *> ResMap; + ResMap _resourceHashMap; + bool _logCacheMiss; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/resource.cpp b/engines/sword25/kernel/resource.cpp new file mode 100644 index 0000000000..40eea2138c --- /dev/null +++ b/engines/sword25/kernel/resource.cpp @@ -0,0 +1,59 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/resource.h" +#include "sword25/kernel/kernel.h" +#include "sword25/package/packagemanager.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "RESOURCE" + +Resource::Resource(const Common::String &fileName, RESOURCE_TYPES type) : + _type(type), + _refCount(0) { + PackageManager *pPM = Kernel::getInstance()->getPackage(); + BS_ASSERT(pPM); + + _fileName = pPM->getAbsolutePath(fileName); +} + +void Resource::release() { + if (_refCount) { + --_refCount; + } else + BS_LOG_WARNINGLN("Released unlocked resource \"%s\".", _fileName.c_str()); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/kernel/resource.h b/engines/sword25/kernel/resource.h new file mode 100644 index 0000000000..5c6108a281 --- /dev/null +++ b/engines/sword25/kernel/resource.h @@ -0,0 +1,110 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_RESOURCE_H +#define SWORD25_RESOURCE_H + +#include "common/list.h" +#include "common/str.h" +#include "sword25/kernel/common.h" + +namespace Sword25 { + +class Kernel; +class ResourceManager; + +class Resource { + friend class ResourceManager; + +public: + enum RESOURCE_TYPES { + TYPE_UNKNOWN, + TYPE_BITMAP, + TYPE_ANIMATION, + TYPE_SOUND, + TYPE_FONT + }; + + Resource(const Common::String &uniqueFileName, RESOURCE_TYPES type); + + /** + * Prevents the resource from being released. + * @remarks This method allows a resource to be locked multiple times. + **/ + void addReference() { + ++_refCount; + } + + /** + * Cancels a previous lock + * @remarks The resource can still be released more times than it was 'locked', although it is + * not recommended. + **/ + void release(); + + /** + * Returns the current lock count for the resource + * @return The current lock count + **/ + int getLockCount() const { + return _refCount; + } + + /** + * Returns the absolute path of the given resource + */ + const Common::String &getFileName() const { + return _fileName; + } + + /** + * Returns a resource's type + */ + uint getType() const { + return _type; + } + +protected: + virtual ~Resource() {} + +private: + Common::String _fileName; ///< The absolute filename + uint _refCount; ///< The number of locks + uint _type; ///< The type of the resource + Common::List<Resource *>::iterator _iterator; ///< Points to the resource position in the LRU list +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/resservice.h b/engines/sword25/kernel/resservice.h new file mode 100644 index 0000000000..a0f2669231 --- /dev/null +++ b/engines/sword25/kernel/resservice.h @@ -0,0 +1,74 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_RESOURCESERVICE_H +#define SWORD25_RESOURCESERVICE_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/service.h" +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/resmanager.h" + +namespace Sword25 { + +class Resource; + +class ResourceService : public Service { +public: + ResourceService(Kernel *pKernel) : Service(pKernel) { + ResourceManager *pResource = pKernel->getResourceManager(); + pResource->registerResourceService(this); + } + + virtual ~ResourceService() {} + + + /** + * Loads a resource + * @return Returns the resource if successful, otherwise NULL + */ + virtual Resource *loadResource(const Common::String &fileName) = 0; + + /** + * Checks whether the given name can be loaded by the resource service + * @param FileName Dateiname + * @return Returns true if the resource can be loaded. + */ + virtual bool canLoadResource(const Common::String &fileName) = 0; + +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/kernel/service.h b/engines/sword25/kernel/service.h new file mode 100644 index 0000000000..ef8858bb7d --- /dev/null +++ b/engines/sword25/kernel/service.h @@ -0,0 +1,75 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + * BS_Service + * ------------- + * This is the base class for all engine services. + * A serivce is an essential part of the engine, ex. the graphics system. + * This was intended to allow, for example, different plug in modules for + * different kinds of hardware and/or systems. + * The services are created at runtime via the kernel method NewService and NEVER with new. + * + * Autor: Malte Thiesen +*/ + +#ifndef SWORD25_SERVICE_H +#define SWORD25_SERVICE_H + +// Includes +#include "sword25/kernel/common.h" + +namespace Sword25 { + +// Klassendefinition +class Kernel; + +class Service { +private: + Kernel *_pKernel; + +protected: + Service(Kernel *pKernel) : _pKernel(pKernel) {} + + Kernel *GetKernel() const { + return _pKernel; + } + +public: + virtual ~Service() {} +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/geometry.cpp b/engines/sword25/math/geometry.cpp new file mode 100644 index 0000000000..bad6fcdb06 --- /dev/null +++ b/engines/sword25/math/geometry.cpp @@ -0,0 +1,49 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/math/geometry.h" + +namespace Sword25 { + +#define BS_LOG_PREFIX "GEOMETRY" + +Geometry::Geometry(Kernel *pKernel) : Service(pKernel) { + if (!registerScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); +} + + +} // End of namespace Sword25 diff --git a/engines/sword25/math/geometry.h b/engines/sword25/math/geometry.h new file mode 100644 index 0000000000..78aa30696e --- /dev/null +++ b/engines/sword25/math/geometry.h @@ -0,0 +1,56 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_GEOMETRY_H +#define SWORD25_GEOMETRY_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/service.h" + +namespace Sword25 { + +class Kernel; + +class Geometry : public Service { +public: + Geometry(Kernel *pKernel); + virtual ~Geometry() {} + +private: + bool registerScriptBindings(); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/geometry_script.cpp b/engines/sword25/math/geometry_script.cpp new file mode 100644 index 0000000000..8882d5e588 --- /dev/null +++ b/engines/sword25/math/geometry_script.cpp @@ -0,0 +1,492 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "common/array.h" +#include "sword25/gfx/graphicengine.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/kernel.h" +#include "sword25/script/script.h" +#include "sword25/script/luabindhelper.h" + +#include "sword25/math/geometry.h" +#include "sword25/math/region.h" +#include "sword25/math/regionregistry.h" +#include "sword25/math/walkregion.h" +#include "sword25/math/vertex.h" + +namespace Sword25 { + +// These strings are defined as #defines to enable compile-time string composition +#define REGION_CLASS_NAME "Geo.Region" +#define WALKREGION_CLASS_NAME "Geo.WalkRegion" + +// How luaL_checkudata, only without that no error is generated. +static void *my_checkudata(lua_State *L, int ud, const char *tname) { + int top = lua_gettop(L); + + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + // lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ + LuaBindhelper::getMetatable(L, tname); + /* does it have the correct mt? */ + if (lua_rawequal(L, -1, -2)) { + lua_settop(L, top); + return p; + } + } + } + + lua_settop(L, top); + return NULL; +} + +static void newUintUserData(lua_State *L, uint value) { + void *userData = lua_newuserdata(L, sizeof(value)); + memcpy(userData, &value, sizeof(value)); +} + +static bool isValidPolygonDefinition(lua_State *L) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // Ensure that we actually consider a table + if (!lua_istable(L, -1)) { + luaL_error(L, "Invalid polygon definition. Unexpected type, \"table\" needed."); + return false; + } + + int tableSize = luaL_getn(L, -1); + + // Make sure that there are at least three Vertecies + if (tableSize < 6) { + luaL_error(L, "Invalid polygon definition. At least three vertecies needed."); + return false; + } + + // Make sure that the number of table elements is divisible by two. + // Since any two elements is a vertex, an odd number of elements is not allowed + if ((tableSize % 2) != 0) { + luaL_error(L, "Invalid polygon definition. Even number of table elements needed."); + return false; + } + + // Ensure that all elements in the table are of type Number + for (int i = 1; i <= tableSize; i++) { + lua_rawgeti(L, -1, i); + if (!lua_isnumber(L, -1)) { + luaL_error(L, "Invalid polygon definition. All table elements have to be numbers."); + return false; + } + lua_pop(L, 1); + } + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return true; +} + +static void tablePolygonToPolygon(lua_State *L, Polygon &polygon) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // Ensure that a valid polygon definition is on the stack. + // It is not necessary to catch the return value, since all errors are reported on luaL_error + // End script. + isValidPolygonDefinition(L); + + int vertexCount = luaL_getn(L, -1) / 2; + + // Memory is reserved for Vertecies + Common::Array<Vertex> vertices; + vertices.reserve(vertexCount); + + // Create Vertecies + for (int i = 0; i < vertexCount; i++) { + // X Value + lua_rawgeti(L, -1, (i * 2) + 1); + int X = static_cast<int>(lua_tonumber(L, -1)); + lua_pop(L, 1); + + // Y Value + lua_rawgeti(L, -1, (i * 2) + 2); + int Y = static_cast<int>(lua_tonumber(L, -1)); + lua_pop(L, 1); + + // Vertex + vertices.push_back(Vertex(X, Y)); + } + BS_ASSERT((int)vertices.size() == vertexCount); + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + // Create polygon + polygon.init(vertexCount, &vertices[0]); +} + +static uint tableRegionToRegion(lua_State *L, const char *className) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // You can define a region in Lua in two ways: + // 1. A table that defines a polygon (polgon = table with numbers, which define + // two consecutive numbers per vertex) + // 2. A table containing more polygon definitions + // Then the first polygon is the contour of the region, and the following are holes + // defined in the first polygon. + + // It may be passed only one parameter, and this must be a table + if (lua_gettop(L) != 1 || !lua_istable(L, -1)) { + luaL_error(L, "First and only parameter has to be of type \"table\"."); + return 0; + } + + uint regionHandle = 0; + if (!strcmp(className, REGION_CLASS_NAME)) { + regionHandle = Region::create(Region::RT_REGION); + } else if (!strcmp(className, WALKREGION_CLASS_NAME)) { + regionHandle = WalkRegion::create(Region::RT_WALKREGION); + } else { + BS_ASSERT(false); + } + + BS_ASSERT(regionHandle); + + // If the first element of the parameter is a number, then case 1 is accepted + // If the first element of the parameter is a table, then case 2 is accepted + // If the first element of the parameter has a different type, there is an error + lua_rawgeti(L, -1, 1); + int firstElementType = lua_type(L, -1); + lua_pop(L, 1); + + switch (firstElementType) { + case LUA_TNUMBER: { + Polygon polygon; + tablePolygonToPolygon(L, polygon); + RegionRegistry::instance().resolveHandle(regionHandle)->init(polygon); + } + break; + + case LUA_TTABLE: { + lua_rawgeti(L, -1, 1); + Polygon polygon; + tablePolygonToPolygon(L, polygon); + lua_pop(L, 1); + + int polygonCount = luaL_getn(L, -1); + if (polygonCount == 1) + RegionRegistry::instance().resolveHandle(regionHandle)->init(polygon); + else { + Common::Array<Polygon> holes; + holes.reserve(polygonCount - 1); + + for (int i = 2; i <= polygonCount; i++) { + lua_rawgeti(L, -1, i); + holes.resize(holes.size() + 1); + tablePolygonToPolygon(L, holes.back()); + lua_pop(L, 1); + } + BS_ASSERT((int)holes.size() == polygonCount - 1); + + RegionRegistry::instance().resolveHandle(regionHandle)->init(polygon, &holes); + } + } + break; + + default: + luaL_error(L, "Illegal region definition."); + return 0; + } + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return regionHandle; +} + +static void newUserdataRegion(lua_State *L, const char *className) { + // Region due to the Lua code to create + // Any errors that occur will be intercepted to the luaL_error + uint regionHandle = tableRegionToRegion(L, className); + BS_ASSERT(regionHandle); + + newUintUserData(L, regionHandle); + // luaL_getmetatable(L, className); + LuaBindhelper::getMetatable(L, className); + BS_ASSERT(!lua_isnil(L, -1)); + lua_setmetatable(L, -2); +} + +static int newRegion(lua_State *L) { + newUserdataRegion(L, REGION_CLASS_NAME); + return 1; +} + +static int newWalkRegion(lua_State *L) { + newUserdataRegion(L, WALKREGION_CLASS_NAME); + return 1; +} + +static const char *GEO_LIBRARY_NAME = "Geo"; + +static const luaL_reg GEO_FUNCTIONS[] = { + {"NewRegion", newRegion}, + {"NewWalkRegion", newWalkRegion}, + {0, 0} +}; + +static Region *checkRegion(lua_State *L) { + // The first parameter must be of type 'userdata', and the Metatable class Geo.Region or Geo.WalkRegion + uint *regionHandlePtr; + if ((regionHandlePtr = reinterpret_cast<uint *>(my_checkudata(L, 1, REGION_CLASS_NAME))) != 0 || + (regionHandlePtr = reinterpret_cast<uint *>(my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) { + return RegionRegistry::instance().resolveHandle(*regionHandlePtr); + } else { + luaL_argcheck(L, 0, 1, "'" REGION_CLASS_NAME "' expected"); + } + + // Compilation fix. Execution never reaches this point + return 0; +} + +static int r_isValid(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + lua_pushbooleancpp(L, pR->isValid()); + return 1; +} + +static int r_getX(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + lua_pushnumber(L, pR->getPosX()); + return 1; +} + +static int r_getY(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + lua_pushnumber(L, pR->getPosY()); + return 1; +} + +static int r_getPos(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + Vertex::vertexToLuaVertex(L, pR->getPosition()); + return 1; +} + +static int r_isPointInRegion(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + Vertex vertex; + Vertex::luaVertexToVertex(L, 2, vertex); + lua_pushbooleancpp(L, pR->isPointInRegion(vertex)); + return 1; +} + +static int r_setPos(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + Vertex vertex; + Vertex::luaVertexToVertex(L, 2, vertex); + pR->setPos(vertex.x, vertex.y); + + return 0; +} + +static int r_setX(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + pR->setPosX(static_cast<int>(luaL_checknumber(L, 2))); + + return 0; +} + +static int r_setY(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + pR->setPosY(static_cast<int>(luaL_checknumber(L, 2))); + + return 0; +} + +static void drawPolygon(const Polygon &polygon, uint color, const Vertex &offset) { + GraphicEngine *pGE = Kernel::getInstance()->getGfx(); + BS_ASSERT(pGE); + + for (int i = 0; i < polygon.vertexCount - 1; i++) + pGE->drawDebugLine(polygon.vertices[i] + offset, polygon.vertices[i + 1] + offset, color); + + pGE->drawDebugLine(polygon.vertices[polygon.vertexCount - 1] + offset, polygon.vertices[0] + offset, color); +} + +static void drawRegion(const Region ®ion, uint color, const Vertex &offset) { + drawPolygon(region.getContour(), color, offset); + for (int i = 0; i < region.getHoleCount(); i++) + drawPolygon(region.getHole(i), color, offset); +} + +static int r_draw(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + + switch (lua_gettop(L)) { + case 3: { + Vertex offset; + Vertex::luaVertexToVertex(L, 3, offset); + drawRegion(*pR, GraphicEngine::luaColorToARGBColor(L, 2), offset); + } + break; + + case 2: + drawRegion(*pR, GraphicEngine::luaColorToARGBColor(L, 2), Vertex(0, 0)); + break; + + default: + drawRegion(*pR, BS_RGB(255, 255, 255), Vertex(0, 0)); + } + + return 0; +} + +static int r_getCentroid(lua_State *L) { + Region *RPtr = checkRegion(L); + BS_ASSERT(RPtr); + + Vertex::vertexToLuaVertex(L, RPtr->getCentroid()); + + return 1; +} + +static int r_delete(lua_State *L) { + Region *pR = checkRegion(L); + BS_ASSERT(pR); + delete pR; + return 0; +} + +static const luaL_reg REGION_METHODS[] = { + {"SetPos", r_setPos}, + {"SetX", r_setX}, + {"SetY", r_setY}, + {"GetPos", r_getPos}, + {"IsPointInRegion", r_isPointInRegion}, + {"GetX", r_getX}, + {"GetY", r_getY}, + {"IsValid", r_isValid}, + {"Draw", r_draw}, + {"GetCentroid", r_getCentroid}, + {0, 0} +}; + +static WalkRegion *checkWalkRegion(lua_State *L) { + // The first parameter must be of type 'userdate', and the Metatable class Geo.WalkRegion + uint regionHandle; + if ((regionHandle = *reinterpret_cast<uint *>(my_checkudata(L, 1, WALKREGION_CLASS_NAME))) != 0) { + return reinterpret_cast<WalkRegion *>(RegionRegistry::instance().resolveHandle(regionHandle)); + } else { + luaL_argcheck(L, 0, 1, "'" WALKREGION_CLASS_NAME "' expected"); + } + + // Compilation fix. Execution never reaches this point + return 0; +} + +static int wr_getPath(lua_State *L) { + WalkRegion *pWR = checkWalkRegion(L); + BS_ASSERT(pWR); + + Vertex start; + Vertex::luaVertexToVertex(L, 2, start); + Vertex end; + Vertex::luaVertexToVertex(L, 3, end); + BS_Path path; + if (pWR->queryPath(start, end, path)) { + lua_newtable(L); + BS_Path::const_iterator it = path.begin(); + for (; it != path.end(); it++) { + lua_pushnumber(L, (it - path.begin()) + 1); + Vertex::vertexToLuaVertex(L, *it); + lua_settable(L, -3); + } + } else + lua_pushnil(L); + + return 1; +} + +static const luaL_reg WALKREGION_METHODS[] = { + {"GetPath", wr_getPath}, + {0, 0} +}; + +bool Geometry::registerScriptBindings() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ScriptEngine *pScript = pKernel->getScript(); + BS_ASSERT(pScript); + lua_State *L = static_cast< lua_State *>(pScript->getScriptObject()); + BS_ASSERT(L); + + if (!LuaBindhelper::addMethodsToClass(L, REGION_CLASS_NAME, REGION_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, WALKREGION_CLASS_NAME, REGION_METHODS)) return false; + if (!LuaBindhelper::addMethodsToClass(L, WALKREGION_CLASS_NAME, WALKREGION_METHODS)) return false; + + if (!LuaBindhelper::setClassGCHandler(L, REGION_CLASS_NAME, r_delete)) return false; + if (!LuaBindhelper::setClassGCHandler(L, WALKREGION_CLASS_NAME, r_delete)) return false; + + if (!LuaBindhelper::addFunctionsToLib(L, GEO_LIBRARY_NAME, GEO_FUNCTIONS)) return false; + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/math/line.h b/engines/sword25/math/line.h new file mode 100644 index 0000000000..d57fce68f7 --- /dev/null +++ b/engines/sword25/math/line.h @@ -0,0 +1,198 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + BS_Line + ------- + This class contains only static methods, which have to do with straight line + segments. There is no real straight line segment class. Calculations will be + used with polygons, and it is important the process of starting and selecting + endpoints of lines is dynamic. This would prhobit a polygon from a set + being formed by fixed line segments + + Autor: Malte Thiesen +*/ + +#ifndef SWORD25_LINE_H +#define SWORD25_LINE_H + +#include "sword25/kernel/common.h" + +namespace Sword25 { + +class Line { +public: + /** + * Determines whether a piont is left of a line + * @param a The start point of a line + * @param b The end point of a line + * @param c The test point + * @return Returns true if the point is to the left of the line. + * If the point is to the right of the line or on the line, false is returned. + */ + static bool isVertexLeft(const Vertex &a, const Vertex &b, const Vertex &c) { + return triangleArea2(a, b, c) > 0; + } + + static bool isVertexLeftOn(const Vertex &a, const Vertex &b, const Vertex &c) { + return triangleArea2(a, b, c) >= 0; + } + + /** + * Determines whether a piont is right of a line + * @param a The start point of a line + * @param b The end point of a line + * @param c The test point + * @return Returns true if the point is to the right of the line. + * If the point is to the right of the line or on the line, false is returned. + */ + static bool isVertexRight(const Vertex &a, const Vertex &b, const Vertex &c) { + return triangleArea2(a, b, c) < 0; + } + + static bool isVertexRightOn(const Vertex &a, const Vertex &b, const Vertex &c) { + return triangleArea2(a, b, c) <= 0; + } + + /** + * Determines whether a piont is on a line + * @param a The start point of a line + * @param b The end point of a line + * @param c The test point + * @return Returns true if the point is on the line, false otherwise. + */ + static bool isVertexOn(const Vertex &a, const Vertex &b, const Vertex &c) { + return triangleArea2(a, b, c) == 0; + } + + enum VERTEX_CLASSIFICATION { + LEFT, + RIGHT, + ON + }; + + /** + * Determines where a point is relative to a line. + * @param a The start point of a line + * @param b The end point of a line + * @param c The test point + * @return LEFT is returned if the point is to the left of the line. + * RIGHT is returned if the point is to the right of the line. + * ON is returned if the point is on the line. + */ + static VERTEX_CLASSIFICATION classifyVertexToLine(const Vertex &a, const Vertex &b, const Vertex &c) { + int area = triangleArea2(a, b, c); + if (area > 0) return LEFT; + if (area < 0) return RIGHT; + return ON; + } + + /** + * Determines whether two lines intersect + * @param a The start point of the first line + * @param b The end point of the first line + * @param c The start point of the second line + * @param d The end point of the second line + * @remark In cases where a line only touches the other, false is returned (improper intersection) + */ + static bool doesIntersectProperly(const Vertex &a, const Vertex &b, const Vertex &c, const Vertex &d) { + VERTEX_CLASSIFICATION class1 = classifyVertexToLine(a, b, c); + VERTEX_CLASSIFICATION class2 = classifyVertexToLine(a, b, d); + VERTEX_CLASSIFICATION class3 = classifyVertexToLine(c, d, a); + VERTEX_CLASSIFICATION class4 = classifyVertexToLine(c, d, b); + + if (class1 == ON || class2 == ON || class3 == ON || class4 == ON) return false; + + return ((class1 == LEFT) ^(class2 == LEFT)) && ((class3 == LEFT) ^(class4 == LEFT)); + } + + /** + * Determines whether a point is on a line segment + * @param a The start point of a line + * @param b The end point of a line + * @param c The test point + */ + static bool isOnLine(const Vertex &a, const Vertex &b, const Vertex &c) { + // The items must all be Collinear, otherwise don't bothering testing the point + if (triangleArea2(a, b, c) != 0) return false; + + // If the line segment is not vertical, check on the x-axis, otherwise the y-axis + if (a.x != b.x) { + return ((a.x <= c.x) && + (c.x <= b.x)) || + ((a.x >= c.x) && + (c.x >= b.x)); + } else { + return ((a.y <= c.y) && + (c.y <= b.y)) || + ((a.y >= c.y) && + (c.y >= b.y)); + } + } + + static bool isOnLineStrict(const Vertex &a, const Vertex &b, const Vertex &c) { + // The items must all be Collinear, otherwise don't bothering testing the point + if (triangleArea2(a, b, c) != 0) return false; + + // If the line segment is not vertical, check on the x-axis, otherwise the y-axis + if (a.x != b.x) { + return ((a.x < c.x) && + (c.x < b.x)) || + ((a.x > c.x) && + (c.x > b.x)); + } else { + return ((a.y < c.y) && + (c.y < b.y)) || + ((a.y > c.y) && + (c.y > b.y)); + } + } + +private: + /** + * Return double the size of the triangle defined by the three passed points. + * + * The result is positive if the points are arrange counterclockwise, + * and negative if they are arranged counter-clockwise. + */ + static int triangleArea2(const Vertex &a, const Vertex &b, const Vertex &c) { + return a.x * b.y - a.y * b.x + + a.y * c.x - a.x * c.y + + b.x * c.y - c.x * b.y; + } +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/polygon.cpp b/engines/sword25/math/polygon.cpp new file mode 100644 index 0000000000..a83a8aa1dd --- /dev/null +++ b/engines/sword25/math/polygon.cpp @@ -0,0 +1,491 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include <math.h> + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +#include "sword25/math/polygon.h" +#include "sword25/math/line.h" + +namespace Sword25 { + +Polygon::Polygon() : vertexCount(0), vertices(NULL) { +} + +Polygon::Polygon(int vertexCount_, const Vertex *vertices_) : vertexCount(0), vertices(NULL) { + init(vertexCount_, vertices_); +} + +Polygon::Polygon(const Polygon &other) : Persistable(other), vertexCount(0), vertices(NULL) { + init(other.vertexCount, other.vertices); +} + +Polygon::Polygon(InputPersistenceBlock &Reader) : vertexCount(0), vertices(NULL) { + unpersist(Reader); +} + +Polygon::~Polygon() { + delete[] vertices; +} + +bool Polygon::init(int vertexCount_, const Vertex *vertices_) { + // Rember the old obstate to restore it if an error occurs whilst initialising it with the new data + int oldvertexCount = this->vertexCount; + Vertex *oldvertices = this->vertices; + + this->vertexCount = vertexCount_; + this->vertices = new Vertex[vertexCount_ + 1]; + memcpy(this->vertices, vertices_, sizeof(Vertex) * vertexCount_); + // TODO: + // Duplicate and remove redundant vertecies (Superflous = 3 co-linear verts) + // _WeedRepeatedvertices(); + // The first vertex is repeated at the end of the vertex array; this simplifies + // some algorithms, running through the edges and thus can save the overflow control. + this->vertices[vertexCount_] = this->vertices[0]; + + // If the polygon is self-intersecting, the object state is restore, and an error signalled + if (checkForSelfIntersection()) { + delete[] this->vertices; + this->vertices = oldvertices; + this->vertexCount = oldvertexCount; + + // BS_LOG_ERROR("POLYGON: Tried to create a self-intersecting polygon.\n"); + return false; + } + + // Release old vertex list + delete[] oldvertices; + + // Calculate properties of the polygon + _isCW = computeIsCW(); + _isConvex = computeIsConvex(); + _centroid = computeCentroid(); + + return true; +} + +// Review the order of the vertices +// --------------------------------- + +bool Polygon::isCW() const { + return _isCW; +} + +bool Polygon::isCCW() const { + return !isCW(); +} + +bool Polygon::computeIsCW() const { + if (vertexCount) { + // Find the vertex on extreme bottom right + int v2Index = findLRVertexIndex(); + + // Find the vertex before and after it + int v1Index = (v2Index + (vertexCount - 1)) % vertexCount; + int v3Index = (v2Index + 1) % vertexCount; + + // Cross product form + // If the cross product of the vertex lying fartherest bottom left is positive, + // the vertecies arrranged in a clockwise order. Otherwise counter-clockwise + if (crossProduct(vertices[v1Index], vertices[v2Index], vertices[v3Index]) >= 0) + return true; + } + + return false; +} + +int Polygon::findLRVertexIndex() const { + if (vertexCount) { + int curIndex = 0; + int maxX = vertices[0].x; + int maxY = vertices[0].y; + + for (int i = 1; i < vertexCount; i++) { + if (vertices[i].y > maxY || + (vertices[i].y == maxY && vertices[i].x > maxX)) { + maxX = vertices[i].x; + maxY = vertices[i].y; + curIndex = i; + } + } + + return curIndex; + } + + return -1; +} + +// Testing for Convex / Concave +// ------------------------ + +bool Polygon::isConvex() const { + return _isConvex; +} + +bool Polygon::isConcave() const { + return !isConvex(); +} + +bool Polygon::computeIsConvex() const { + // Polygons with three or less vertices can only be convex + if (vertexCount <= 3) return true; + + // All angles in the polygon computed will have the same direction sign if the polygon is convex + int flag = 0; + for (int i = 0; i < vertexCount; i++) { + // Determine the next two vertecies to check + int j = (i + 1) % vertexCount; + int k = (i + 2) % vertexCount; + + // Calculate the cross product of the three vertecies + int cross = crossProduct(vertices[i], vertices[j], vertices[k]); + + // The lower two bits of the flag represent the following: + // 0: negative angle occurred + // 1: positive angle occurred + + // The sign of the current angle is recorded in Flag + if (cross < 0) + flag |= 1; + else if (cross > 0) + flag |= 2; + + // If flag is 3, there are both positive and negative angles; so the polygon is concave + if (flag == 3) + return false; + } + + // Polygon is convex + return true; +} + +// Make a determine vertex order +// ----------------------------- + +void Polygon::ensureCWOrder() { + if (!isCW()) + reverseVertexOrder(); +} + +void Polygon::ensureCCWOrder() { + if (!isCCW()) + reverseVertexOrder(); +} + +// Reverse the order of vertecies +// ------------------------------ + +void Polygon::reverseVertexOrder() { + // vertices are exchanged in pairs, until the list has been completely reversed + for (int i = 0; i < vertexCount / 2; i++) { + Vertex tempVertex = vertices[i]; + vertices[i] = vertices[vertexCount - i - 1]; + vertices[vertexCount - i - 1] = tempVertex; + } + + // Vertexordnung neu berechnen. + _isCW = computeIsCW(); +} + +// Cross Product +// ------------- + +int Polygon::crossProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const { + return (v2.x - v1.x) * (v3.y - v2.y) - + (v2.y - v1.y) * (v3.x - v2.x); +} + +// Scalar Product +// -------------- + +int Polygon::dotProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const { + return (v1.x - v2.x) * (v3.x - v2.x) + + (v1.y - v2.y) * (v3.x - v2.y); +} + +// Check for self-intersections +// ---------------------------- + +bool Polygon::checkForSelfIntersection() const { + // TODO: Finish this + /* + float AngleSum = 0.0f; + for (int i = 0; i < vertexCount; i++) { + int j = (i + 1) % vertexCount; + int k = (i + 2) % vertexCount; + + float Dot = DotProduct(vertices[i], vertices[j], vertices[k]); + + // Skalarproduct normalisieren + float Length1 = sqrt((vertices[i].x - vertices[j].x) * (vertices[i].x - vertices[j].x) + + (vertices[i].y - vertices[j].y) * (vertices[i].y - vertices[j].y)); + float Length2 = sqrt((vertices[k].x - vertices[j].x) * (vertices[k].x - vertices[j].x) + + (vertices[k].y - vertices[j].y) * (vertices[k].y - vertices[j].y)); + float Norm = Length1 * Length2; + + if (Norm > 0.0f) { + Dot /= Norm; + AngleSum += acos(Dot); + } + } + */ + + return false; +} + +// Move +// ---- + +void Polygon::operator+=(const Vertex &delta) { + // Move all vertecies + for (int i = 0; i < vertexCount; i++) + vertices[i] += delta; + + // Shift the focus + _centroid += delta; +} + +// Line of Sight +// ------------- + +bool Polygon::isLineInterior(const Vertex &a, const Vertex &b) const { + // Both points have to be in the polygon + if (!isPointInPolygon(a, true) || !isPointInPolygon(b, true)) + return false; + + // If the points are identical, the line is trivially within the polygon + if (a == b) + return true; + + // Test whether the line intersects a line segment strictly (proper intersection) + for (int i = 0; i < vertexCount; i++) { + int j = (i + 1) % vertexCount; + const Vertex &vs = vertices[i]; + const Vertex &ve = vertices[j]; + + // If the line intersects a line segment strictly (proper cross section) the line is not in the polygon + if (Line::doesIntersectProperly(a, b, vs, ve)) + return false; + + // If one of the two line items is on the edge and the other is to the right of the edge, + // then the line is not completely within the polygon + if (Line::isOnLineStrict(vs, ve, a) && Line::isVertexRight(vs, ve, b)) + return false; + if (Line::isOnLineStrict(vs, ve, b) && Line::isVertexRight(vs, ve, a)) + return false; + + // If one of the two line items is on a vertex, the line traces into the polygon + if ((a == vs) && !isLineInCone(i, b, true)) + return false; + if ((b == vs) && !isLineInCone(i, a, true)) + return false; + } + + return true; +} + +bool Polygon::isLineExterior(const Vertex &a, const Vertex &b) const { + // Neither of the two points must be strictly in the polygon (on the edge is allowed) + if (isPointInPolygon(a, false) || isPointInPolygon(b, false)) + return false; + + // If the points are identical, the line is trivially outside of the polygon + if (a == b) + return true; + + // Test whether the line intersects a line segment strictly (proper intersection) + for (int i = 0; i < vertexCount; i++) { + int j = (i + 1) % vertexCount; + const Vertex &vs = vertices[i]; + const Vertex &ve = vertices[j]; + + // If the line intersects a line segment strictly (proper intersection), then + // the line is partially inside the polygon + if (Line::doesIntersectProperly(a, b, vs, ve)) + return false; + + // If one of the two line items is on the edge and the other is to the right of the edge, + // the line is not completely outside the polygon + if (Line::isOnLineStrict(vs, ve, a) && Line::isVertexLeft(vs, ve, b)) + return false; + if (Line::isOnLineStrict(vs, ve, b) && Line::isVertexLeft(vs, ve, a)) + return false; + + // If one of the lwo line items is on a vertex, the line must not run into the polygon + if ((a == vs) && isLineInCone(i, b, false)) + return false; + if ((b == vs) && isLineInCone(i, a, false)) + return false; + + // If the vertex with start and end point is collinear, (a vs) and (b, vs) is not in the polygon + if (Line::isOnLine(a, b, vs)) { + if (isLineInCone(i, a, false)) + return false; + if (isLineInCone(i, b, false)) + return false; + } + } + + return true; +} + +bool Polygon::isLineInCone(int startVertexIndex, const Vertex &endVertex, bool includeEdges) const { + const Vertex &startVertex = vertices[startVertexIndex]; + const Vertex &nextVertex = vertices[(startVertexIndex + 1) % vertexCount]; + const Vertex &prevVertex = vertices[(startVertexIndex + vertexCount - 1) % vertexCount]; + + if (Line::isVertexLeftOn(prevVertex, startVertex, nextVertex)) { + if (includeEdges) + return Line::isVertexLeftOn(endVertex, startVertex, nextVertex) && + Line::isVertexLeftOn(startVertex, endVertex, prevVertex); + else + return Line::isVertexLeft(endVertex, startVertex, nextVertex) && + Line::isVertexLeft(startVertex, endVertex, prevVertex); + } else { + if (includeEdges) + return !(Line::isVertexLeft(endVertex, startVertex, prevVertex) && + Line::isVertexLeft(startVertex, endVertex, nextVertex)); + else + return !(Line::isVertexLeftOn(endVertex, startVertex, prevVertex) && + Line::isVertexLeftOn(startVertex, endVertex, nextVertex)); + } +} + +// Point-Polygon Tests +// ------------------- + +bool Polygon::isPointInPolygon(int x, int y, bool borderBelongsToPolygon) const { + return isPointInPolygon(Vertex(x, y), borderBelongsToPolygon); +} + +bool Polygon::isPointInPolygon(const Vertex &point, bool edgesBelongToPolygon) const { + int rcross = 0; // Number of right-side overlaps + int lcross = 0; // Number of left-side overlaps + + // Each edge is checked whether it cuts the outgoing stream from the point + for (int i = 0; i < vertexCount; i++) { + const Vertex &edgeStart = vertices[i]; + const Vertex &edgeEnd = vertices[(i + 1) % vertexCount]; + + // A vertex is a point? Then it lies on one edge of the polygon + if (point == edgeStart) + return edgesBelongToPolygon; + + if ((edgeStart.y > point.y) != (edgeEnd.y > point.y)) { + int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y); + int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y); + if ((term1 > 0) == (term2 >= 0)) + rcross++; + } + + if ((edgeStart.y < point.y) != (edgeEnd.y < point.y)) { + int term1 = (edgeStart.x - point.x) * (edgeEnd.y - point.y) - (edgeEnd.x - point.x) * (edgeStart.y - point.y); + int term2 = (edgeEnd.y - point.y) - (edgeStart.y - edgeEnd.y); + if ((term1 < 0) == (term2 <= 0)) + lcross++; + } + } + + // The point is on an adge, if the number of left and right intersections have the same even numbers + if ((rcross % 2) != (lcross % 2)) + return edgesBelongToPolygon; + + // The point is strictly inside the polygon if and only if the number of overlaps is odd + if ((rcross % 2) == 1) + return true; + else + return false; +} + +bool Polygon::persist(OutputPersistenceBlock &writer) { + writer.write(vertexCount); + for (int i = 0; i < vertexCount; ++i) { + writer.write(vertices[i].x); + writer.write(vertices[i].y); + } + + return true; +} + +bool Polygon::unpersist(InputPersistenceBlock &reader) { + int storedvertexCount; + reader.read(storedvertexCount); + + Common::Array<Vertex> storedvertices; + for (int i = 0; i < storedvertexCount; ++i) { + int x, y; + reader.read(x); + reader.read(y); + storedvertices.push_back(Vertex(x, y)); + } + + init(storedvertexCount, &storedvertices[0]); + + return reader.isGood(); +} + +// Main Focus +// ---------- + +Vertex Polygon::getCentroid() const { + return _centroid; +} + +Vertex Polygon::computeCentroid() const { + // Area of the polygon is calculated + int doubleArea = 0; + for (int i = 0; i < vertexCount; ++i) { + doubleArea += vertices[i].x * vertices[i + 1].y - vertices[i + 1].x * vertices[i].y; + } + + // Avoid division by zero in the next step + if (doubleArea == 0) + return Vertex(); + + // Calculate centroid + Vertex centroid; + for (int i = 0; i < vertexCount; ++i) { + int area = vertices[i].x * vertices[i + 1].y - vertices[i + 1].x * vertices[i].y; + centroid.x += (vertices[i].x + vertices[i + 1].x) * area; + centroid.y += (vertices[i].y + vertices[i + 1].y) * area; + } + centroid.x /= 3 * doubleArea; + centroid.y /= 3 * doubleArea; + + return centroid; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/math/polygon.h b/engines/sword25/math/polygon.h new file mode 100644 index 0000000000..1b2a0b191c --- /dev/null +++ b/engines/sword25/math/polygon.h @@ -0,0 +1,267 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_POLYGON_H +#define SWORD25_POLYGON_H + +// Includes +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "sword25/math/vertex.h" + +namespace Sword25 { + +class Vertex; + +/** + @brief Eine Polygonklasse. +*/ +class Polygon : public Persistable { +public: + /** + * Creates an object of type #BS_Polygon, containing 0 Vertecies. + * + * With the method Init(), Vertices can be added in later + */ + Polygon(); + + /** + * Copy constructor + */ + Polygon(const Polygon &other); + + /** + * Creates a polygon using persisted data + */ + Polygon(InputPersistenceBlock &reader); + + /** + * Creaes an object of type #BS_Polygon, and assigns Vertices to it + * @param VertexCount The number of vertices being passed + * @param Vertecies An array of BS_Vertex objects representing the vertices in the polygon. + * @remark The Vertecies that define a polygon must not have any self-intersections. + * If the polygon does have self-intersections, then an empty polygon object is created. + */ + Polygon(int vertexCount_, const Vertex *vertices_); + + /** + * Deletes the BS_Polygon object + */ + virtual ~Polygon(); + + /** + * Initialises the BS_Polygon with a list of Vertecies. + * + * The Vertices need to define a polygon must not have self-intersections. + * If a polygon already has verticies, this will re-initialise it with the new list. + * + * @param VertexCount The number of vertices being passed + * @param Vertecies An array of BS_Vertex objects representing the vertices in the polygon. + * @return Returns false if the Vertecies have self-intersections. In this case, + * the object is not initialised. + */ + bool init(int vertexCount_, const Vertex *vertices_); + + // + // ** Exploratory methods ** + // + + /** + * Checks whether the Vertecies of the polygon are arranged in a clockwise direction. + * @return Returns true if the Vertecies of the polygon are arranged clockwise or co-planar. + * Returns false if the Vertecies of the polygon are arrange counter-clockwise. + * @remark This method only returns a meaningful result if the polygon has at least three Vertecies. + */ + bool isCW() const; + + /** + * Checks whether the Vertices of the polygon are arranged in a counter-clockwise direction. + * @return Returns true if the Vertecies of the polygon are arranged counter-clockwise. + * Returns false if the Vertecies of the polygon are arranged clockwise or co-planar. + * @remark This method only returns a meaningful result if the polygon has at least three Vertecies. + */ + bool isCCW() const; + + /** + * Checks whether the polygon is convex. + * @return Returns true if the polygon is convex. Returns false if the polygon is concave. + * @remark This method only returns a meaningful result if the polygon has at least three Vertecies. + */ + bool isConvex() const; + + /** + * Checks whether the polygon is concave. + * @return Returns true if the polygon is concave. Returns false if the polygon is convex. + * @remark This method only returns a meaningful result if the polygon has at least three Vertecies. + */ + bool isConcave() const; + + /** + * Checks whether a point is inside the polygon + * @param Vertex A Vertex with the co-ordinates of the point to be tested. + * @param BorderBelongsToPolygon Specifies whether the edge of the polygon should be considered + * @return Returns true if the point is inside the polygon, false if it is outside. + */ + bool isPointInPolygon(const Vertex &vertex, bool borderBelongsToPolygon = true) const; + + /** + * Checks whether a point is inside the polygon + * @param X The X position of the point + * @param Y The Y position of the point + * @param BorderBelongsToPolygon Specifies whether the edge of the polygon should be considered + * @return Returns true if the point is inside the polygon, false if it is outside. + */ + bool isPointInPolygon(int x, int y, bool borderBelongsToPolygon = true) const; + + /** + * Returns the focus/centroid of the polygon + */ + Vertex getCentroid() const; + + // Edge belongs to the polygon + // Polygon must be CW + bool isLineInterior(const Vertex &a, const Vertex &b) const; + // Edge does not belong to the polygon + // Polygon must be CW + bool isLineExterior(const Vertex &a, const Vertex &b) const; + + // + // Manipulation methods + // + + /** + * Ensures that the Vertecies of the polygon are arranged in a clockwise direction + */ + void ensureCWOrder(); + + /** + * Ensures that the Vertecies of the polygon are arranged in a counter-clockwise direction + */ + void ensureCCWOrder(); + + /** + * Reverses the Vertecies order. + */ + void reverseVertexOrder(); + + /** + * Moves the polygon. + * @param Delta The vertex around the polygon to be moved. + */ + void operator+=(const Vertex &delta); + + // + //------------------ + // + + /// Specifies the number of Vertecies in the Vertecies array. + int vertexCount; + /// COntains the Vertecies of the polygon + Vertex *vertices; + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + + Polygon &operator=(const Polygon &p) { + init(p.vertexCount, p.vertices); + return *this; + } + +private: + bool _isCW; + bool _isConvex; + Vertex _centroid; + + /** + * Computes the centroid of the polygon. + */ + Vertex computeCentroid() const; + + /** + * Determines how the Vertecies of the polygon are arranged. + * @return Returns true if the Vertecies are arranged in a clockwise + * direction, otherwise false. + */ + bool computeIsCW() const; + + /** + * Determines whether the polygon is convex or concave. + * @return Returns true if the polygon is convex, otherwise false. + */ + bool computeIsConvex() const; + + /** + * Calculates the cross product of three Vertecies + * @param V1 The first Vertex + * @param V2 The second Vertex + * @param V3 The third Vertex + * @return Returns the cross-product of the three vertecies + * @todo This method would be better as a method of the BS_Vertex class + */ + int crossProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const; + + /** + * Computes the scalar product of two vectors spanning three vertecies + * + * The vectors are spanned by V2->V1 and V2->V3 + * + * @param V1 The first Vertex + * @param V2 The second Vertex + * @param V3 The third Vertex + * @return Returns the dot product of the three Vertecies. + * @todo This method would be better as a method of the BS_Vertex class + */ + int dotProduct(const Vertex &v1, const Vertex &v2, const Vertex &v3) const; + + /** + * Checks whether the polygon is self-intersecting + * @return Returns true if the polygon is self-intersecting. + * Returns false if the polygon is not self-intersecting. + */ + bool checkForSelfIntersection() const; + + /** + * Find the vertex of the polygon that is located below the right-most point, + * and returns it's index in the vertex array. + * @return Returns the index of the vertex at the bottom-right of the polygon. + * Returns -1 if the vertex list is empty. + */ + int findLRVertexIndex() const; + + bool isLineInCone(int startVertexIndex, const Vertex &endVertex, bool includeEdges) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/region.cpp b/engines/sword25/math/region.cpp new file mode 100644 index 0000000000..ae9b76d077 --- /dev/null +++ b/engines/sword25/math/region.cpp @@ -0,0 +1,354 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" + +#include "sword25/math/region.h" +#include "sword25/math/walkregion.h" +#include "sword25/math/regionregistry.h" + +#define BS_LOG_PREFIX "REGION" + +namespace Sword25 { + +Region::Region() : _valid(false), _type(RT_REGION) { + RegionRegistry::instance().registerObject(this); +} + +Region::Region(InputPersistenceBlock &reader, uint handle) : _valid(false), _type(RT_REGION) { + RegionRegistry::instance().registerObject(this, handle); + unpersist(reader); +} + +uint Region::create(REGION_TYPE type) { + Region *regionPtr = NULL; + switch (type) { + case RT_REGION: + regionPtr = new Region(); + break; + + case RT_WALKREGION: + regionPtr = new WalkRegion(); + break; + + default: + BS_ASSERT(true); + } + + return RegionRegistry::instance().resolvePtr(regionPtr); +} + +uint Region::create(InputPersistenceBlock &reader, uint handle) { + // Read type + uint type; + reader.read(type); + + // Depending on the type, create a new BS_Region or BS_WalkRegion object + Region *regionPtr = NULL; + if (type == RT_REGION) { + regionPtr = new Region(reader, handle); + } else if (type == RT_WALKREGION) { + regionPtr = new WalkRegion(reader, handle); + } else { + BS_ASSERT(false); + } + + return RegionRegistry::instance().resolvePtr(regionPtr); +} + +Region::~Region() { + RegionRegistry::instance().deregisterObject(this); +} + +bool Region::init(const Polygon &contour, const Common::Array<Polygon> *pHoles) { + // Reset object state + _valid = false; + _position = Vertex(0, 0); + _polygons.clear(); + + // Reserve sufficient space for countour and holes in the polygon list + if (pHoles) + _polygons.reserve(1 + pHoles->size()); + else + _polygons.reserve(1); + + // The first polygon will be the contour + _polygons.push_back(Polygon()); + _polygons[0].init(contour.vertexCount, contour.vertices); + // Make sure that the Vertecies in the Contour are arranged in a clockwise direction + _polygons[0].ensureCWOrder(); + + // Place the hole polygons in the following positions + if (pHoles) { + for (uint i = 0; i < pHoles->size(); ++i) { + _polygons.push_back(Polygon()); + _polygons[i + 1].init((*pHoles)[i].vertexCount, (*pHoles)[i].vertices); + _polygons[i + 1].ensureCWOrder(); + } + } + + + // Initialise bounding box + updateBoundingBox(); + + _valid = true; + return true; +} + +void Region::updateBoundingBox() { + if (_polygons[0].vertexCount) { + int minX = _polygons[0].vertices[0].x; + int maxX = _polygons[0].vertices[0].x; + int minY = _polygons[0].vertices[0].y; + int maxY = _polygons[0].vertices[0].y; + + for (int i = 1; i < _polygons[0].vertexCount; i++) { + if (_polygons[0].vertices[i].x < minX) minX = _polygons[0].vertices[i].x; + else if (_polygons[0].vertices[i].x > maxX) maxX = _polygons[0].vertices[i].x; + if (_polygons[0].vertices[i].y < minY) minY = _polygons[0].vertices[i].y; + else if (_polygons[0].vertices[i].y > maxY) maxY = _polygons[0].vertices[i].y; + } + + _boundingBox = Common::Rect(minX, minY, maxX + 1, maxY + 1); + } +} + +// Position Changes +void Region::setPos(int x, int y) { + // Calculate the difference between the old and new position + Vertex delta(x - _position.x, y - _position.y); + + // Save the new position + _position = Vertex(x, y); + + // Move all the vertecies + for (uint i = 0; i < _polygons.size(); ++i) { + _polygons[i] += delta; + } + + // Update the bounding box + updateBoundingBox(); +} + +void Region::setPosX(int x) { + setPos(x, _position.y); +} + +void Region::setPosY(int y) { + setPos(_position.x, y); +} + +// Point-Region Tests +bool Region::isPointInRegion(int x, int y) const { + // Test whether the point is in the bounding box + if (_boundingBox.contains(x, y)) { + // Test whether the point is in the contour + if (_polygons[0].isPointInPolygon(x, y, true)) { + // Test whether the point is in a hole + for (uint i = 1; i < _polygons.size(); i++) { + if (_polygons[i].isPointInPolygon(x, y, false)) + return false; + } + + return true; + } + } + + return false; +} + +bool Region::isPointInRegion(const Vertex &vertex) const { + return isPointInRegion(vertex.x, vertex.y); +} + +Vertex Region::findClosestRegionPoint(const Vertex &point) const { + // Determine whether the point is inside a hole. If that is the case, the closest + // point on the edge of the hole is determined + int polygonIdx = 0; + { + for (uint i = 1; i < _polygons.size(); ++i) { + if (_polygons[i].isPointInPolygon(point)) { + polygonIdx = i; + break; + } + } + } + + const Polygon &polygon = _polygons[polygonIdx]; + + BS_ASSERT(polygon.vertexCount > 1); + + // For each line of the polygon, calculate the point that is cloest to the given point + // The point of this set with the smallest distance to the given point is the result. + Vertex closestVertex = findClosestPointOnLine(polygon.vertices[0], polygon.vertices[1], point); + int closestVertexDistance2 = closestVertex.distance(point); + for (int i = 1; i < polygon.vertexCount; ++i) { + int j = (i + 1) % polygon.vertexCount; + + Vertex curVertex = findClosestPointOnLine(polygon.vertices[i], polygon.vertices[j], point); + if (curVertex.distance(point) < closestVertexDistance2) { + closestVertex = curVertex; + closestVertexDistance2 = curVertex.distance(point); + } + } + + // Determine whether the point is really within the region. This must not be so, as a result of rounding + // errors can occur at the edge of polygons + if (isPointInRegion(closestVertex)) + return closestVertex; + else { + // Try to construct a point within the region - 8 points are tested in the immediate vacinity + // of the point + if (isPointInRegion(closestVertex + Vertex(-2, -2))) + return closestVertex + Vertex(-2, -2); + else if (isPointInRegion(closestVertex + Vertex(0, -2))) + return closestVertex + Vertex(0, -2); + else if (isPointInRegion(closestVertex + Vertex(2, -2))) + return closestVertex + Vertex(2, -2); + else if (isPointInRegion(closestVertex + Vertex(-2, 0))) + return closestVertex + Vertex(-2, 0); + else if (isPointInRegion(closestVertex + Vertex(0, 2))) + return closestVertex + Vertex(0, 2); + else if (isPointInRegion(closestVertex + Vertex(-2, 2))) + return closestVertex + Vertex(-2, 2); + else if (isPointInRegion(closestVertex + Vertex(-2, 0))) + return closestVertex + Vertex(2, 2); + else if (isPointInRegion(closestVertex + Vertex(2, 2))) + return closestVertex + Vertex(2, 2); + + // If no point could be found that way that lies within the region, find the next point + closestVertex = polygon.vertices[0]; + int shortestVertexDistance2 = polygon.vertices[0].distance2(point); + { + for (int i = 1; i < polygon.vertexCount; i++) { + int curDistance2 = polygon.vertices[i].distance2(point); + if (curDistance2 < shortestVertexDistance2) { + closestVertex = polygon.vertices[i]; + shortestVertexDistance2 = curDistance2; + } + } + } + + BS_LOG_WARNINGLN("Clostest vertex forced because edgepoint was outside region."); + return closestVertex; + } +} + +Vertex Region::findClosestPointOnLine(const Vertex &lineStart, const Vertex &lineEnd, const Vertex point) const { + float vector1X = static_cast<float>(point.x - lineStart.x); + float vector1Y = static_cast<float>(point.y - lineStart.y); + float vector2X = static_cast<float>(lineEnd.x - lineStart.x); + float vector2Y = static_cast<float>(lineEnd.y - lineStart.y); + float vector2Length = sqrtf(vector2X * vector2X + vector2Y * vector2Y); + vector2X /= vector2Length; + vector2Y /= vector2Length; + float distance = sqrtf(static_cast<float>((lineStart.x - lineEnd.x) * (lineStart.x - lineEnd.x) + + (lineStart.y - lineEnd.y) * (lineStart.y - lineEnd.y))); + float dot = vector1X * vector2X + vector1Y * vector2Y; + + if (dot <= 0) + return lineStart; + if (dot >= distance) + return lineEnd; + + Vertex vector3(static_cast<int>(vector2X * dot + 0.5f), static_cast<int>(vector2Y * dot + 0.5f)); + return lineStart + vector3; +} + +// Line of Sight +bool Region::isLineOfSight(const Vertex &a, const Vertex &b) const { + BS_ASSERT(_polygons.size()); + + // The line must be within the contour polygon, and outside of any hole polygons + Common::Array<Polygon>::const_iterator iter = _polygons.begin(); + if (!(*iter).isLineInterior(a, b)) return false; + for (iter++; iter != _polygons.end(); iter++) + if (!(*iter).isLineExterior(a, b)) return false; + + return true; +} + +// Persistence +bool Region::persist(OutputPersistenceBlock &writer) { + bool Result = true; + + writer.write(static_cast<uint>(_type)); + writer.write(_valid); + writer.write(_position.x); + writer.write(_position.y); + + writer.write(_polygons.size()); + Common::Array<Polygon>::iterator It = _polygons.begin(); + while (It != _polygons.end()) { + Result &= It->persist(writer); + ++It; + } + + writer.write(_boundingBox.left); + writer.write(_boundingBox.top); + writer.write(_boundingBox.right); + writer.write(_boundingBox.bottom); + + return Result; +} + +bool Region::unpersist(InputPersistenceBlock &reader) { + reader.read(_valid); + reader.read(_position.x); + reader.read(_position.y); + + _polygons.clear(); + uint PolygonCount; + reader.read(PolygonCount); + for (uint i = 0; i < PolygonCount; ++i) { + _polygons.push_back(Polygon(reader)); + } + + reader.read(_boundingBox.left); + reader.read(_boundingBox.top); + reader.read(_boundingBox.right); + reader.read(_boundingBox.bottom); + + return reader.isGood(); +} + +Vertex Region::getCentroid() const { + if (_polygons.size() > 0) + return _polygons[0].getCentroid(); + return + Vertex(); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/math/region.h b/engines/sword25/math/region.h new file mode 100644 index 0000000000..fac9f98bb6 --- /dev/null +++ b/engines/sword25/math/region.h @@ -0,0 +1,241 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_REGION_H +#define SWORD25_REGION_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "sword25/math/vertex.h" +#include "sword25/math/polygon.h" +#include "common/rect.h" + +namespace Sword25 { + +/** + * This class is the base class of all regions. + * + * The IsValid() method can be queried to see whether the object is in a valid state. + * If this is not the case, the method Init() is the only method that may be invoked. + * This class guarantees that the Vertecies outline of the hole, and the polygons are + * arranged in a clockwise direction, so that the polygon working algorithms will + * work properly. + */ +class Region : public Persistable { +protected: + /** + * Creates a new BS_Region object + * + * After creation the object is invaild (IsValid() return false), but a call can + * be made later on to Init() to set up the region into a valid state. + */ + Region(); + + Region(InputPersistenceBlock &reader, uint handle); + +public: + enum REGION_TYPE { + RT_REGION, + RT_WALKREGION + }; + + static uint create(REGION_TYPE type); + static uint create(InputPersistenceBlock &reader, uint handle = 0); + + virtual ~Region(); + + /** + * Initialises a BS_Region object + * @param Contour A polygon indicating the outline of the region + * @param pHoles A pointer to an array of polygons representing the hole state in the region. + * If the region has no holes, it must be passed as NULL. The default value is NULL. + * @return Returns true if the initialisation was successful, otherwise false. + * @remark If the region was already initialised, the old state will be deleted. + */ + virtual bool init(const Polygon &contour, const Common::Array<Polygon> *pHoles = NULL); + + // + // Exploratory Methods + // + + /** + * Specifies whether the object is in a valid state + * @return Returns true if the object is in a valid state, otherwise false. + * @remark Invalid objects can be made valid by calling Init with a valid state. + */ + bool isValid() const { + return _valid; + } + + /** + * Returns the position of the region + */ + const Vertex &getPosition() const { + return _position; + } + + /** + * Returns the X position of the region + */ + int getPosX() const { + return _position.x; + } + + /** + * Returns the Y position of the region + */ + int getPosY() const { + return _position.y; + } + + /** + * Indicates whether a point is inside the region + * @param Vertex A verex with the co-ordinates of the test point + * @return Returns true if the point is within the region, otherwise false. + */ + bool isPointInRegion(const Vertex &vertex) const; + + /** + * Indicates whether a point is inside the region + * @param X The X position + * @param Y The Y position + * @return Returns true if the point is within the region, otherwise false. + */ + bool isPointInRegion(int x, int y) const; + + /** + * Returns the countour of the region + */ + const Polygon &getContour() const { + return _polygons[0]; + } + + /** + * Returns the number of polygons in the hole region + */ + int getHoleCount() const { + return static_cast<int>(_polygons.size() - 1); + } + + /** + * Returns a specific hole polygon in the region + * @param i The number of the hole to return. + * The index must be between 0 and GetHoleCount() - 1. + * @return Returns the desired hole polygon + */ + inline const Polygon &getHole(uint i) const; + + /** + * For a point outside the region, finds the closest point inside the region + * @param Point The point that is outside the region + * @return Returns the point within the region which is closest + * @remark This method does not always work with pixel accuracy. + * One should not therefore rely on the fact that there is really no point in + * the region which is closer to the given point. + */ + Vertex findClosestRegionPoint(const Vertex &point) const; + + /** + * Returns the centroid for the region + */ + Vertex getCentroid() const; + + bool isLineOfSight(const Vertex &a, const Vertex &b) const; + + // + // Manipulation Methods + // + + /** + * Sets the position of the region + * @param X The new X psoition of the region + * @param Y The new Y psoition of the region + */ + virtual void setPos(int x, int y); + + /** + * Sets the X position of the region + * @param X The new X position of the region + */ + void setPosX(int x); + + /** + * Sets the Y position of the region + * @param Y The new Y position of the region + */ + void setPosY(int y); + + // + // Manipulation Methods + // + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +protected: + /// This specifies the type of object + REGION_TYPE _type; + /// This variable indicates whether the current object state is valid + bool _valid; + /// This vertex is the position of the region + Vertex _position; + /// This array contains all the polygons that define the region. The first element of + // the array is the contour, all others are the holes + Common::Array<Polygon> _polygons; + /// The bounding box for the region + Common::Rect _boundingBox; + + /** + * Updates the bounding box of the region. + */ + void updateBoundingBox(); + + /** + * Find the point on a line which is closest to another point + * @param LineStart The start of the line + * @param LineEnd The end of the line + * @param Point The point to be compared against + * @return Returns the point on the line which is cloest to the passed point. + */ + Vertex findClosestPointOnLine(const Vertex &lineStart, const Vertex &lineEnd, const Vertex point) const; +}; + +inline const Polygon &Region::getHole(uint i) const { + BS_ASSERT(i < _polygons.size() - 1); + return _polygons[i + 1]; +} + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/regionregistry.cpp b/engines/sword25/math/regionregistry.cpp new file mode 100644 index 0000000000..40909aebad --- /dev/null +++ b/engines/sword25/math/regionregistry.cpp @@ -0,0 +1,106 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "REGIONREGISTRY" + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/math/regionregistry.h" +#include "sword25/math/region.h" + +DECLARE_SINGLETON(Sword25::RegionRegistry) + +namespace Sword25 { + +void RegionRegistry::logErrorLn(const char *message) const { + BS_LOG_ERRORLN(message); +} + +void RegionRegistry::logWarningLn(const char *message) const { + BS_LOG_WARNINGLN(message); +} + +bool RegionRegistry::persist(OutputPersistenceBlock &writer) { + bool result = true; + + // write out the next handle + writer.write(_nextHandle); + + // Number of regions to write + writer.write(_handle2PtrMap.size()); + + // Persist all the BS_Regions + HANDLE2PTR_MAP::const_iterator iter = _handle2PtrMap.begin(); + while (iter != _handle2PtrMap.end()) { + // Handle persistence + writer.write(iter->_key); + + // Persist object + result &= iter->_value->persist(writer); + + ++iter; + } + + return result; +} + +bool RegionRegistry::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + // read in the next handle + reader.read(_nextHandle); + + // Destroy all existing BS_Regions +//FIXME: This doesn't seem right - the value is being deleted but not the actual hash node itself? + while (!_handle2PtrMap.empty()) + delete _handle2PtrMap.begin()->_value; + + // read in the number of BS_Regions + uint regionCount; + reader.read(regionCount); + + // Restore all the BS_Regions objects + for (uint i = 0; i < regionCount; ++i) { + // Handle read + uint handle; + reader.read(handle); + + // BS_Region restore + result &= Region::create(reader, handle) != 0; + } + + return reader.isGood() && result; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/math/regionregistry.h b/engines/sword25/math/regionregistry.h new file mode 100644 index 0000000000..560d4ae4a9 --- /dev/null +++ b/engines/sword25/math/regionregistry.h @@ -0,0 +1,63 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_REGIONREGISTRY_H +#define SWORD25_REGIONREGISTRY_H + +#include "common/singleton.h" + +#include "sword25/kernel/common.h" +#include "sword25/kernel/persistable.h" +#include "sword25/kernel/objectregistry.h" + +namespace Sword25 { + +class Region; + +class RegionRegistry : + public ObjectRegistry<Region>, + public Persistable, + public Common::Singleton<RegionRegistry> { +public: + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + virtual void logErrorLn(const char *message) const; + virtual void logWarningLn(const char *message) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/vertex.cpp b/engines/sword25/math/vertex.cpp new file mode 100644 index 0000000000..d9a709ab49 --- /dev/null +++ b/engines/sword25/math/vertex.cpp @@ -0,0 +1,86 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/math/vertex.h" + +#include "sword25/util/lua/lua.h" +#include "sword25/util/lua/lauxlib.h" + +namespace Sword25 { + +Vertex &Vertex::luaVertexToVertex(lua_State *L, int stackIndex, Vertex &vertex) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // Ensure that we actually consider a table + luaL_checktype(L, stackIndex, LUA_TTABLE); + + // Read X Component + lua_pushstring(L, "X"); + lua_gettable(L, stackIndex); + if (!lua_isnumber(L, -1)) luaL_argcheck(L, 0, stackIndex, "the X component has to be a number"); + vertex.x = static_cast<int>(lua_tonumber(L, -1)); + lua_pop(L, 1); + + // Read Y Component + lua_pushstring(L, "Y"); + lua_gettable(L, stackIndex); + if (!lua_isnumber(L, -1)) luaL_argcheck(L, 0, stackIndex, "the Y component has to be a number"); + vertex.y = static_cast<int>(lua_tonumber(L, -1)); + lua_pop(L, 1); + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return vertex; +} + +void Vertex::vertexToLuaVertex(lua_State *L, const Vertex &vertex) { + // Create New Table + lua_newtable(L); + + // X value is written to table + lua_pushstring(L, "X"); + lua_pushnumber(L, vertex.x); + lua_settable(L, -3); + + // Y value is written to table + lua_pushstring(L, "Y"); + lua_pushnumber(L, vertex.y); + lua_settable(L, -3); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/math/vertex.h b/engines/sword25/math/vertex.h new file mode 100644 index 0000000000..b923841a0f --- /dev/null +++ b/engines/sword25/math/vertex.h @@ -0,0 +1,189 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + BS_Vertex + --------- + + Autor: Malte Thiesen +*/ + +#ifndef SWORD25_VERTEX_H +#define SWORD25_VERTEX_H + +// Includes +#include <math.h> +#include "sword25/kernel/common.h" +#include "sword25/util/lua/lua.h" + +#if defined(MACOSX) || defined(SOLARIS) || defined(__MINGW32__) +// Older versions of Mac OS X didn't supply a powf function, so using it +// will cause a binary incompatibility when trying to run a binary built +// on a newer OS X release on an olderr one. And Solaris 8 doesn't provide +// powf, floorf, fabsf etc. at all. +// Cross-compiled MinGW32 toolchains suffer from a cross-compile bug in +// libstdc++. math/stubs.o should be empty, but it comes with a symbol for +// powf, resulting in a linker error because of multiple definitions. +// Hence we re-define them here. The only potential drawback is that it +// might be a little bit slower this way. +#define powf(x,y) ((float)pow(x,y)) +#define floorf(x) ((float)floor(x)) +#define fabsf(x) ((float)fabs(x)) +#define sqrtf(x) ((float)sqrt(x)) +#define atan2f(x,y) ((float)atan2(x,y)) +#endif + +namespace Sword25 { + +/** + * Defines a 2-D Vertex + */ +class Vertex { +public: + Vertex() : x(0), y(0) {} + Vertex(int x_, int y_) { + this->x = x_; + this->y = y_; + } + + int x; + int y; + + /** + * Compares two Vertecies. + */ + inline bool operator==(const Vertex &rhs) const { + if (x == rhs.x && y == rhs.y) return true; + return false; + } + /** + * Compares two Vertecies. + */ + inline bool operator!=(const Vertex &rhs) const { + if (x != rhs.x || y != rhs.y) return true; + return false; + } + /** + * Adds a vertex to vertex + */ + inline void operator+=(const Vertex &delta) { + x += delta.x; + y += delta.y; + } + + /** + * Subtracts a vertex from a vertex + */ + inline void operator-=(const Vertex &delta) { + x -= delta.x; + y -= delta.y; + } + + /** + * Adds two vertecies + */ + inline Vertex operator+(const Vertex &delta) const { + return Vertex(x + delta.x, y + delta.y); + } + + /** + * Subtracts two vertecies + */ + inline Vertex operator-(const Vertex &delta) const { + return Vertex(x - delta.x, y - delta.y); + } + + /** + * Calculates the square of the distance between two Vertecies. + * @param Vertex The vertex for which the distance is to be calculated + * @return Returns the square of the distance between itself and the passed vertex + * @remark If only distances should be compared, this method should be used because + * it is faster than Distance() + */ + inline int distance2(const Vertex &vertex) const { + return (x - vertex.x) * (x - vertex.x) + (y - vertex.y) * (y - vertex.y); + } + + /** + * Calculates the square of the distance between two Vertecies. + * @param Vertex The vertex for which the distance is to be calculated + * @return Returns the square of the distance between itself and the passed vertex + * @remark If only distances should be compared, Distance2() should be used, since it is faster. + */ + inline int distance(const Vertex &vertex) const { + return (int)(sqrtf(static_cast<float>(distance2(vertex))) + 0.5); + } + + /** + * Calculates the cross product of the vertex with another vertex. Here the Vertecies will be + * interpreted as vectors. + * @param Vertex The second vertex + * @return Returns the cross product of this vertex and the passed vertex. + */ + inline int computeCrossProduct(const Vertex &vertex) const { + return x * vertex.y - vertex.x * y; + } + + /** + * Returns the dot product of this vertex with another vertex. Here the Vertecies are interpreted as vectors. + * @param Vertex The second vertex + * @return Returns the dot product of this vertex and the passed vertex. + */ + inline int computeDotProduct(const Vertex &vertex) const { + return x * vertex.x + y * vertex.y; + } + + /** + * Calculates the angle between this vertex and another vertex. Here the Vertecies are interpreted as vectors. + * @param Vertex The second vertex + * @return Returns the angle between this vertex and the passed vertex in radians. + */ + inline float computeAngle(const Vertex &vertex) const { + return atan2f(static_cast<float>(computeCrossProduct(vertex)), static_cast<float>(computeDotProduct(vertex))); + } + + /** + * Calculates the length of the vector + */ + inline float computeLength() const { + return sqrtf(static_cast<float>(x * x + y * y)); + } + + static Vertex &luaVertexToVertex(lua_State *L, int StackIndex, Vertex &vertex); + static void vertexToLuaVertex(lua_State *L, const Vertex &vertex); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/math/walkregion.cpp b/engines/sword25/math/walkregion.cpp new file mode 100644 index 0000000000..51818bc9e8 --- /dev/null +++ b/engines/sword25/math/walkregion.cpp @@ -0,0 +1,401 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/inputpersistenceblock.h" +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/math/walkregion.h" +#include "sword25/math/line.h" + +#define BS_LOG_PREFIX "WALKREGION" + +namespace Sword25 { + +static const int Infinity = 0x7fffffff; + +WalkRegion::WalkRegion() { + _type = RT_WALKREGION; +} + +WalkRegion::WalkRegion(InputPersistenceBlock &reader, uint handle) : + Region(reader, handle) { + _type = RT_WALKREGION; + unpersist(reader); +} + +WalkRegion::~WalkRegion() { +} + +bool WalkRegion::init(const Polygon &contour, const Common::Array<Polygon> *pHoles) { + // Default initialisation of the region + if (!Region::init(contour, pHoles)) return false; + + // Prepare structures for pathfinding + initNodeVector(); + computeVisibilityMatrix(); + + // Signal success + return true; +} + +bool WalkRegion::queryPath(Vertex startPoint, Vertex endPoint, BS_Path &path) { + BS_ASSERT(path.empty()); + + // If the start and finish are identical, no path can be found trivially + if (startPoint == endPoint) + return true; + + // Ensure that the start and finish are valid and find new start points if either + // are outside the polygon + if (!checkAndPrepareStartAndEnd(startPoint, endPoint)) return false; + + // If between the start and point a line of sight exists, then it can be returned. + if (isLineOfSight(startPoint, endPoint)) { + path.push_back(startPoint); + path.push_back(endPoint); + return true; + } + + return findPath(startPoint, endPoint, path); +} + +struct DijkstraNode { + typedef Common::Array<DijkstraNode> Container; + typedef Container::iterator Iter; + typedef Container::const_iterator ConstIter; + + DijkstraNode() : cost(Infinity), chosen(false) {} + ConstIter parentIter; + int cost; + bool chosen; +}; + +static void initDijkstraNodes(DijkstraNode::Container &dijkstraNodes, const Region ®ion, + const Vertex &start, const Common::Array<Vertex> &nodes) { + // Allocate sufficient space in the array + dijkstraNodes.resize(nodes.size()); + + // Initialise all the nodes which are visible from the starting node + DijkstraNode::Iter dijkstraIter = dijkstraNodes.begin(); + for (Common::Array<Vertex>::const_iterator nodesIter = nodes.begin(); + nodesIter != nodes.end(); nodesIter++, dijkstraIter++) { + (*dijkstraIter).parentIter = dijkstraNodes.end(); + if (region.isLineOfSight(*nodesIter, start))(*dijkstraIter).cost = (*nodesIter).distance(start); + } + BS_ASSERT(dijkstraIter == dijkstraNodes.end()); +} + +static DijkstraNode::Iter chooseClosestNode(DijkstraNode::Container &nodes) { + DijkstraNode::Iter closestNodeInter = nodes.end(); + int minCost = Infinity; + + for (DijkstraNode::Iter iter = nodes.begin(); iter != nodes.end(); iter++) { + if (!(*iter).chosen && (*iter).cost < minCost) { + minCost = (*iter).cost; + closestNodeInter = iter; + } + } + + return closestNodeInter; +} + +static void relaxNodes(DijkstraNode::Container &nodes, + const Common::Array< Common::Array<int> > &visibilityMatrix, + const DijkstraNode::ConstIter &curNodeIter) { + // All the successors of the current node that have not been chosen will be + // inserted into the boundary node list, and the cost will be updated if + // a shorter path has been found to them. + + int curNodeIndex = curNodeIter - nodes.begin(); + for (uint i = 0; i < nodes.size(); i++) { + int cost = visibilityMatrix[curNodeIndex][i]; + if (!nodes[i].chosen && cost != Infinity) { + int totalCost = (*curNodeIter).cost + cost; + if (totalCost < nodes[i].cost) { + nodes[i].parentIter = curNodeIter; + nodes[i].cost = totalCost; + } + } + } +} + +static void relaxEndPoint(const Vertex &curNodePos, + const DijkstraNode::ConstIter &curNodeIter, + const Vertex &endPointPos, + DijkstraNode &endPoint, + const Region ®ion) { + if (region.isLineOfSight(curNodePos, endPointPos)) { + int totalCost = (*curNodeIter).cost + curNodePos.distance(endPointPos); + if (totalCost < endPoint.cost) { + endPoint.parentIter = curNodeIter; + endPoint.cost = totalCost; + } + } +} + +template<class T> +void reverseArray(Common::Array<T> &arr) { + const uint size = arr.size(); + if (size < 2) + return; + + for (uint i = 0; i <= (size / 2 - 1); ++i) { + SWAP(arr[i], arr[size - i - 1]); + } +} + +bool WalkRegion::findPath(const Vertex &start, const Vertex &end, BS_Path &path) const { + // This is an implementation of Dijkstra's algorithm + + // Initialise edge node list + DijkstraNode::Container dijkstraNodes; + initDijkstraNodes(dijkstraNodes, *this, start, _nodes); + + // The end point is treated separately, since it does not exist in the visibility graph + DijkstraNode endPoint; + + // Since a node is selected each round from the node list, and can never be selected again + // after that, the maximum number of loop iterations is limited by the number of nodes + for (uint i = 0; i < _nodes.size(); i++) { + // Determine the nearest edge node in the node list + DijkstraNode::Iter nodeInter = chooseClosestNode(dijkstraNodes); + + // If no free nodes are absent from the edge node list, there is no path from start + // to end node. This case should never occur, since the number of loop passes is + // limited, but etter safe than sorry + if (nodeInter == dijkstraNodes.end()) + return false; + + // If the destination point is closer than the point cost, scan can stop + (*nodeInter).chosen = true; + if (endPoint.cost <= (*nodeInter).cost) { + // Insert the end point in the list + path.push_back(end); + + // The list is done in reverse order and inserted into the path + DijkstraNode::ConstIter curNode = endPoint.parentIter; + while (curNode != dijkstraNodes.end()) { + BS_ASSERT((*curNode).chosen); + path.push_back(_nodes[curNode - dijkstraNodes.begin()]); + curNode = (*curNode).parentIter; + } + + // The starting point is inserted into the path + path.push_back(start); + + // The nodes of the path must be untwisted, as they were extracted in reverse order. + // This step could be saved if the path from end to the beginning was desired + reverseArray<Vertex>(path); + + return true; + } + + // Relaxation step for nodes of the graph, and perform the end nodes + relaxNodes(dijkstraNodes, _visibilityMatrix, nodeInter); + relaxEndPoint(_nodes[nodeInter - dijkstraNodes.begin()], nodeInter, end, endPoint, *this); + } + + // If the loop has been completely run through, all the nodes have been chosen, and still + // no path was found. There is therefore no path available + return false; +} + +void WalkRegion::initNodeVector() { + // Empty the Node list + _nodes.clear(); + + // Determine the number of nodes + int nodeCount = 0; + { + for (uint i = 0; i < _polygons.size(); i++) + nodeCount += _polygons[i].vertexCount; + } + + // Knoten-Vector füllen + _nodes.reserve(nodeCount); + { + for (uint j = 0; j < _polygons.size(); j++) + for (int i = 0; i < _polygons[j].vertexCount; i++) + _nodes.push_back(_polygons[j].vertices[i]); + } +} + +void WalkRegion::computeVisibilityMatrix() { + // Initialise visibility matrix + _visibilityMatrix = Common::Array< Common::Array <int> >(); + for (uint idx = 0; idx < _nodes.size(); ++idx) { + Common::Array<int> arr; + for (uint idx2 = 0; idx2 < _nodes.size(); ++idx2) + arr.push_back(Infinity); + + _visibilityMatrix.push_back(arr); + } + + // Calculate visibility been vertecies + for (uint j = 0; j < _nodes.size(); ++j) { + for (uint i = j; i < _nodes.size(); ++i) { + if (isLineOfSight(_nodes[i], _nodes[j])) { + // There is a line of sight, so save the distance between the two + int distance = _nodes[i].distance(_nodes[j]); + _visibilityMatrix[i][j] = distance; + _visibilityMatrix[j][i] = distance; + } else { + // There is no line of sight, so save Infinity as the distance + _visibilityMatrix[i][j] = Infinity; + _visibilityMatrix[j][i] = Infinity; + } + } + } +} + +bool WalkRegion::checkAndPrepareStartAndEnd(Vertex &start, Vertex &end) const { + if (!isPointInRegion(start)) { + Vertex newStart = findClosestRegionPoint(start); + + // Check to make sure the point is really in the region. If not, stop with an error + if (!isPointInRegion(newStart)) { + BS_LOG_ERRORLN("Constructed startpoint ((%d,%d) from (%d,%d)) is not inside the region.", + newStart.x, newStart.y, + start.x, start.y); + return false; + } + + start = newStart; + } + + // If the destination is outside the region, a point is determined that is within the region, + // and that is used as an endpoint instead + if (!isPointInRegion(end)) { + Vertex newEnd = findClosestRegionPoint(end); + + // Make sure that the determined point is really within the region + if (!isPointInRegion(newEnd)) { + BS_LOG_ERRORLN("Constructed endpoint ((%d,%d) from (%d,%d)) is not inside the region.", + newEnd.x, newEnd.y, + end.x, end.y); + return false; + } + + end = newEnd; + } + + // Signal success + return true; +} + +void WalkRegion::setPos(int x, int y) { + // Calculate the difference between old and new position + Vertex Delta(x - _position.x, y - _position.y); + + // Move all the nodes + for (uint i = 0; i < _nodes.size(); i++) + _nodes[i] += Delta; + + // Move regions + Region::setPos(x, y); +} + +bool WalkRegion::persist(OutputPersistenceBlock &writer) { + bool result = true; + + // Persist the parent region + result &= Region::persist(writer); + + // Persist the nodes + writer.write(_nodes.size()); + Common::Array<Vertex>::const_iterator it = _nodes.begin(); + while (it != _nodes.end()) { + writer.write(it->x); + writer.write(it->y); + ++it; + } + + // Persist the visibility matrix + writer.write(_visibilityMatrix.size()); + Common::Array< Common::Array<int> >::const_iterator rowIter = _visibilityMatrix.begin(); + while (rowIter != _visibilityMatrix.end()) { + writer.write(rowIter->size()); + Common::Array<int>::const_iterator colIter = rowIter->begin(); + while (colIter != rowIter->end()) { + writer.write(*colIter); + ++colIter; + } + + ++rowIter; + } + + return result; +} + +bool WalkRegion::unpersist(InputPersistenceBlock &reader) { + bool result = true; + + // The parent object was already loaded in the constructor of BS_Region, so at + // this point only the additional data from BS_WalkRegion needs to be loaded + + // Node load + uint nodeCount; + reader.read(nodeCount); + _nodes.clear(); + _nodes.resize(nodeCount); + Common::Array<Vertex>::iterator it = _nodes.begin(); + while (it != _nodes.end()) { + reader.read(it->x); + reader.read(it->y); + ++it; + } + + // Visibility matrix load + uint rowCount; + reader.read(rowCount); + _visibilityMatrix.clear(); + _visibilityMatrix.resize(rowCount); + Common::Array< Common::Array<int> >::iterator rowIter = _visibilityMatrix.begin(); + while (rowIter != _visibilityMatrix.end()) { + uint colCount; + reader.read(colCount); + rowIter->resize(colCount); + Common::Array<int>::iterator colIter = rowIter->begin(); + while (colIter != rowIter->end()) { + reader.read(*colIter); + ++colIter; + } + + ++rowIter; + } + + return result && reader.isGood(); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/math/walkregion.h b/engines/sword25/math/walkregion.h new file mode 100644 index 0000000000..e8bf40becc --- /dev/null +++ b/engines/sword25/math/walkregion.h @@ -0,0 +1,113 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_WALKREGION_H +#define SWORD25_WALKREGION_H + +#include "common/array.h" +#include "sword25/kernel/common.h" +#include "sword25/math/region.h" + +namespace Sword25 { + +typedef Common::Array<Vertex> BS_Path; + +/** + * This class represents the region in which the main character can move + */ +class WalkRegion : public Region { + friend class Region; + +protected: + WalkRegion(); + WalkRegion(InputPersistenceBlock &Reader, uint handle); + +public: + virtual ~WalkRegion(); + + virtual bool init(const Polygon &contour, const Common::Array<Polygon> *pHoles = 0); + + /** + * Get the shortest path between two points in the region + * + * This method requires that the starting point lies within the region. The end point + * may lie outside the region. Int his case, the end is chosen as the cloest point to it + * that lies within the region. + * + * @param X1 X Co-ordinate of the start point + * @param Y1 Y Co-ordinate of the start point + * @param X2 X Co-ordinate of the end point + * @param Y2 Y Co-ordinate of the end point + * @param Path An empty BS_Path that will be set to the resulting path + * @return Returns false if the result is invalid, otherwise returns true. + */ + bool queryPath(int x1, int y1, int x2, int y2, BS_Path &path) { + return queryPath(Vertex(x1, y1), Vertex(x2, y2), path); + } + + /** + * Get the shortest path between two points in the region. + * + * @param StartPoint The start point + * @param EndPoint The end point + * @param Path An empty BS_Path that will be set to the resulting path + * @return Returns false if the result is invalid, otherwise returns true. + */ + bool queryPath(Vertex startPoint, Vertex endPoint, BS_Path &path); + + virtual void setPos(int x, int y); + + const Common::Array<Vertex> &getNodes() const { + return _nodes; + } + const Common::Array< Common::Array<int> > &getVisibilityMatrix() const { + return _visibilityMatrix; + } + + virtual bool persist(OutputPersistenceBlock &writer); + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + Common::Array<Vertex> _nodes; + Common::Array< Common::Array<int> > _visibilityMatrix; + + void initNodeVector(); + void computeVisibilityMatrix(); + bool checkAndPrepareStartAndEnd(Vertex &start, Vertex &end) const; + bool findPath(const Vertex &start, const Vertex &end, BS_Path &path) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/module.mk b/engines/sword25/module.mk new file mode 100644 index 0000000000..f10c6be5a9 --- /dev/null +++ b/engines/sword25/module.mk @@ -0,0 +1,106 @@ +MODULE := engines/sword25 + +MODULE_OBJS := \ + detection.o \ + sword25.o \ + fmv/movieplayer.o \ + fmv/movieplayer_script.o \ + gfx/animation.o \ + gfx/animationdescription.o \ + gfx/animationresource.o \ + gfx/animationtemplate.o \ + gfx/animationtemplateregistry.o \ + gfx/bitmap.o \ + gfx/bitmapresource.o \ + gfx/dynamicbitmap.o \ + gfx/fontresource.o \ + gfx/framecounter.o \ + gfx/graphicengine.o \ + gfx/graphicengine_script.o \ + gfx/panel.o \ + gfx/renderobject.o \ + gfx/renderobjectmanager.o \ + gfx/renderobjectregistry.o \ + gfx/screenshot.o \ + gfx/staticbitmap.o \ + gfx/text.o \ + gfx/timedrenderobject.o \ + gfx/image/art.o \ + gfx/image/pngloader.o \ + gfx/image/renderedimage.o \ + gfx/image/swimage.o \ + gfx/image/vectorimage.o \ + gfx/image/vectorimagerenderer.o \ + input/inputengine.o \ + input/inputengine_script.o \ + kernel/filesystemutil.o \ + kernel/inputpersistenceblock.o \ + kernel/kernel.o \ + kernel/kernel_script.o \ + kernel/log.o \ + kernel/outputpersistenceblock.o \ + kernel/persistenceservice.o \ + kernel/resmanager.o \ + kernel/resource.o \ + math/geometry.o \ + math/geometry_script.o \ + math/polygon.o \ + math/region.o \ + math/regionregistry.o \ + math/vertex.o \ + math/walkregion.o \ + package/packagemanager.o \ + package/packagemanager_script.o \ + script/luabindhelper.o \ + script/luacallback.o \ + script/luascript.o \ + script/lua_extensions.o \ + sfx/soundengine.o \ + sfx/soundengine_script.o \ + util/lua/lapi.o \ + util/lua/lauxlib.o \ + util/lua/lbaselib.o \ + util/lua/lcode.o \ + util/lua/ldblib.o \ + util/lua/ldebug.o \ + util/lua/ldo.o \ + util/lua/ldump.o \ + util/lua/lfunc.o \ + util/lua/lgc.o \ + util/lua/linit.o \ + util/lua/liolib.o \ + util/lua/llex.o \ + util/lua/lmathlib.o \ + util/lua/lmem.o \ + util/lua/loadlib.o \ + util/lua/lobject.o \ + util/lua/lopcodes.o \ + util/lua/loslib.o \ + util/lua/lparser.o \ + util/lua/lstate.o \ + util/lua/lstring.o \ + util/lua/lstrlib.o \ + util/lua/ltable.o \ + util/lua/ltablib.o \ + util/lua/ltm.o \ + util/lua/lundump.o \ + util/lua/lvm.o \ + util/lua/lzio.o \ + util/lua/print.o \ + util/pluto/pdep.o \ + util/pluto/pluto.o \ + util/pluto/plzio.o + +ifdef USE_THEORADEC +MODULE_OBJS += \ + fmv/theora_decoder.o \ + fmv/yuvtorgba.o +endif + +# This module can be built as a plugin +ifeq ($(ENABLE_SWORD25), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/sword25/package/packagemanager.cpp b/engines/sword25/package/packagemanager.cpp new file mode 100644 index 0000000000..4765d26ed4 --- /dev/null +++ b/engines/sword25/package/packagemanager.cpp @@ -0,0 +1,276 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "PACKAGEMANAGER" + +#include "common/archive.h" +#include "common/config-manager.h" +#include "common/savefile.h" +#include "common/str-array.h" +#include "common/system.h" +#include "common/unzip.h" +#include "sword25/kernel/filesystemutil.h" +#include "sword25/package/packagemanager.h" + +namespace Sword25 { + +const char PATH_SEPARATOR = '/'; + +static Common::String normalizePath(const Common::String &path, const Common::String ¤tDirectory) { + Common::String wholePath = (path.size() >= 1 && path[0] == PATH_SEPARATOR) ? path : currentDirectory + PATH_SEPARATOR + path; + + if (wholePath.size() == 0) { + // The path list has no elements, therefore the root directory is returned + return Common::String(PATH_SEPARATOR); + } + + return Common::normalizePath(wholePath, PATH_SEPARATOR); +} + +PackageManager::PackageManager(Kernel *pKernel) : Service(pKernel), + _currentDirectory(PATH_SEPARATOR), + _rootFolder(ConfMan.get("path")) { + if (!registerScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); +} + +PackageManager::~PackageManager() { + // Free the package list + Common::List<ArchiveEntry *>::iterator i; + for (i = _archiveList.begin(); i != _archiveList.end(); ++i) + delete *i; + +} + +/** + * Scans through the archive list for a specified file + */ +Common::ArchiveMemberPtr PackageManager::getArchiveMember(const Common::String &fileName) { + // Loop through checking each archive + Common::List<ArchiveEntry *>::iterator i; + for (i = _archiveList.begin(); i != _archiveList.end(); ++i) { + if (!fileName.hasPrefix((*i)->_mountPath)) { + // The mount path is in different subtree. Skipping + continue; + } + + // Look into the archive for the desired file + Common::Archive *archiveFolder = (*i)->archive; + + // Construct relative path + Common::String resPath(&fileName.c_str()[(*i)->_mountPath.size()]); + + if (archiveFolder->hasFile(resPath)) { + return archiveFolder->getMember(resPath); + } + } + + return Common::ArchiveMemberPtr(); +} + +bool PackageManager::loadPackage(const Common::String &fileName, const Common::String &mountPosition) { + debug(3, "loadPackage(%s, %s)", fileName.c_str(), mountPosition.c_str()); + + Common::Archive *zipFile = Common::makeZipArchive(fileName); + if (zipFile == NULL) { + BS_LOG_ERRORLN("Unable to mount file \"%s\" to \"%s\"", fileName.c_str(), mountPosition.c_str()); + return false; + } else { + BS_LOGLN("Package '%s' mounted as '%s'.", fileName.c_str(), mountPosition.c_str()); + Common::ArchiveMemberList files; + zipFile->listMembers(files); + debug(3, "Capacity %d", files.size()); + + for (Common::ArchiveMemberList::iterator it = files.begin(); it != files.end(); ++it) + debug(3, "%s", (*it)->getName().c_str()); + + _archiveList.push_front(new ArchiveEntry(zipFile, mountPosition)); + + return true; + } +} + +bool PackageManager::loadDirectoryAsPackage(const Common::String &directoryName, const Common::String &mountPosition) { + Common::FSNode directory(directoryName); + Common::Archive *folderArchive = new Common::FSDirectory(directory, 6); + if (!directory.exists() || (folderArchive == NULL)) { + BS_LOG_ERRORLN("Unable to mount directory \"%s\" to \"%s\".", directoryName.c_str(), mountPosition.c_str()); + return false; + } else { + BS_LOGLN("Directory '%s' mounted as '%s'.", directoryName.c_str(), mountPosition.c_str()); + + Common::ArchiveMemberList files; + folderArchive->listMembers(files); + debug(0, "Capacity %d", files.size()); + + _archiveList.push_front(new ArchiveEntry(folderArchive, mountPosition)); + + return true; + } +} + +byte *PackageManager::getFile(const Common::String &fileName, uint *fileSizePtr) { + const Common::String B25S_EXTENSION(".b25s"); + Common::SeekableReadStream *in; + + if (fileName.hasSuffix(B25S_EXTENSION)) { + // Savegame loading logic + Common::SaveFileManager *sfm = g_system->getSavefileManager(); + Common::InSaveFile *file = sfm->openForLoading( + FileSystemUtil::getPathFilename(fileName)); + if (!file) { + BS_LOG_ERRORLN("Could not load savegame \"%s\".", fileName.c_str()); + return 0; + } + + if (fileSizePtr) + *fileSizePtr = file->size(); + + byte *buffer = new byte[file->size()]; + file->read(buffer, file->size()); + + delete file; + return buffer; + } + + Common::ArchiveMemberPtr fileNode = getArchiveMember(normalizePath(fileName, _currentDirectory)); + if (!fileNode) + return 0; + if (!(in = fileNode->createReadStream())) + return 0; + + // If the filesize is desired, then output the size + if (fileSizePtr) + *fileSizePtr = in->size(); + + // Read the file + byte *buffer = new byte[in->size()]; + int bytesRead = in->read(buffer, in->size()); + delete in; + + if (!bytesRead) { + delete[] buffer; + return NULL; + } + + return buffer; +} + +Common::SeekableReadStream *PackageManager::getStream(const Common::String &fileName) { + Common::SeekableReadStream *in; + Common::ArchiveMemberPtr fileNode = getArchiveMember(normalizePath(fileName, _currentDirectory)); + if (!fileNode) + return 0; + if (!(in = fileNode->createReadStream())) + return 0; + + return in; +} + +Common::String PackageManager::getCurrentDirectory() { + return _currentDirectory; +} + +bool PackageManager::changeDirectory(const Common::String &directory) { + // Get the path elements for the file + _currentDirectory = normalizePath(directory, _currentDirectory); + return true; +} + +Common::String PackageManager::getAbsolutePath(const Common::String &fileName) { + return normalizePath(fileName, _currentDirectory); +} + +bool PackageManager::fileExists(const Common::String &fileName) { + // FIXME: The current Zip implementation doesn't support getting a folder entry, which is needed for detecting + // the English voick pack + if (fileName == "/speech/en") { + // To get around this, change to detecting one of the files in the folder + return getArchiveMember(normalizePath(fileName + "/APO0001.ogg", _currentDirectory)); + } + + Common::ArchiveMemberPtr fileNode = getArchiveMember(normalizePath(fileName, _currentDirectory)); + return fileNode; +} + +int PackageManager::doSearch(Common::ArchiveMemberList &list, const Common::String &filter, const Common::String &path, uint typeFilter) { + Common::String normalizedFilter = normalizePath(filter, _currentDirectory); + int num = 0; + + if (path.size() > 0) + warning("STUB: PackageManager::doSearch(<%s>, <%s>, %d)", filter.c_str(), path.c_str(), typeFilter); + + // Loop through checking each archive + Common::List<ArchiveEntry *>::iterator i; + for (i = _archiveList.begin(); i != _archiveList.end(); ++i) { + Common::ArchiveMemberList memberList; + + if (!normalizedFilter.hasPrefix((*i)->_mountPath)) { + // The mount path is in different subtree. Skipping + continue; + } + + // Construct relative path + Common::String resFilter(&normalizedFilter.c_str()[(*i)->_mountPath.size()]); + + if ((*i)->archive->listMatchingMembers(memberList, resFilter) == 0) + continue; + + // Create a list of the matching names + for (Common::ArchiveMemberList::iterator it = memberList.begin(); it != memberList.end(); ++it) { + if (((typeFilter & PackageManager::FT_DIRECTORY) && (*it)->getName().hasSuffix("/")) || + ((typeFilter & PackageManager::FT_FILE) && !(*it)->getName().hasSuffix("/"))) { + + // Do not add duplicate files + bool found = false; + for (Common::ArchiveMemberList::iterator it1 = list.begin(); it1 != list.end(); ++it1) { + if ((*it1)->getName() == (*it)->getName()) { + found = true; + break; + } + } + + if (!found) + list.push_back(*it); + num++; + } + } + } + + return num; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/package/packagemanager.h b/engines/sword25/package/packagemanager.h new file mode 100644 index 0000000000..03598012a6 --- /dev/null +++ b/engines/sword25/package/packagemanager.h @@ -0,0 +1,205 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + * BS_PackageManager + * ----------------- + * This is the package manager interface, that contains all the methods that a package manager + * must implement. + * In the package manager, note the following: + * 1. It creates a completely new (virtual) directory tree in the packages and directories + * can be mounted. + * 2. To seperate elements of a directory path '/' must be used rather than '\' + * 3. LoadDirectoryAsPackage should only be used for testing. The final release will be + * have all files in packages. + * + * Autor: Malte Thiesen, $author$ + */ + +#ifndef SWORD25_PACKAGE_MANAGER_H +#define SWORD25_PACKAGE_MANAGER_H + +#include "common/archive.h" +#include "common/array.h" +#include "common/fs.h" +#include "common/str.h" + +#include "sword25/kernel/common.h" +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/service.h" + +namespace Sword25 { + +// Class definitions + +/** + * The Package Manager interface + * + * 1. It creates a completely new (virtual) directory tree in the packages and directories + * can be mounted. + * 2. To seperate elements of a directory path '/' must be used rather than '\' + * 3. LoadDirectoryAsPackage should only be used for testing. The final release will be + * have all files in packages. + */ +class PackageManager : public Service { +private: + class ArchiveEntry { + public: + Common::Archive *archive; + Common::String _mountPath; + + ArchiveEntry(Common::Archive *archive_, const Common::String &mountPath_): + archive(archive_), _mountPath(mountPath_) { + } + ~ArchiveEntry() { + delete archive; + } + }; + + Common::String _currentDirectory; + Common::FSNode _rootFolder; + Common::List<ArchiveEntry *> _archiveList; + + Common::ArchiveMemberPtr getArchiveMember(const Common::String &fileName); + +public: + PackageManager(Kernel *pKernel); + ~PackageManager(); + + enum FILE_TYPES { + FT_DIRECTORY = (1 << 0), + FT_FILE = (1 << 1) + }; + + /** + * Mounts the contents of a package in the directory specified in the directory tree. + * @param FileName The filename of the package to mount + * @param MountPosition The directory name under which the package should be mounted + * @return Returns true if the mount was successful, otherwise false. + */ + bool loadPackage(const Common::String &fileName, const Common::String &mountPosition); + /** + * Mounts the contents of a directory in the specified directory in the directory tree. + * @param The name of the directory to mount + * @param MountPosition The directory name under which the package should be mounted + * @return Returns true if the mount was successful, otherwise false. + */ + bool loadDirectoryAsPackage(const Common::String &directoryName, const Common::String &mountPosition); + /** + * Downloads a file from the directory tree + * @param FileName The filename of the file to load + * @param pFileSize Pointer to the variable that will contain the size of the loaded file. The deafult is NULL. + * @return Specifies a pointer to the loaded data of the file + * @remark The client must not forget to release the data of the file using BE_DELETE_A. + */ + byte *getFile(const Common::String &fileName, uint *pFileSize = NULL); + + /** + * Returns a stream from file file from the directory tree + * @param FileName The filename of the file to load + * @return Pointer to the stream object + */ + Common::SeekableReadStream *getStream(const Common::String &fileName); + /** + * Downloads an XML file and prefixes it with an XML Version key, since the XML files don't contain it, + * and it is required for ScummVM to correctly parse the XML. + * @param FileName The filename of the file to load + * @param pFileSize Pointer to the variable that will contain the size of the loaded file. The deafult is NULL. + * @return Specifies a pointer to the loaded data of the file + * @remark The client must not forget to release the data of the file using BE_DELETE_A. + */ + char *getXmlFile(const Common::String &fileName, uint *pFileSize = NULL) { + const char *versionStr = "<?xml version=\"1.0\"?>"; + uint fileSize; + char *data = (char *)getFile(fileName, &fileSize); + char *result = (char *)malloc(fileSize + strlen(versionStr) + 1); + strcpy(result, versionStr); + Common::copy(data, data + fileSize, result + strlen(versionStr)); + result[fileSize + strlen(versionStr)] = '\0'; + + delete[] data; + if (pFileSize) + *pFileSize = fileSize + strlen(versionStr); + + return result; + } + + /** + * Returns the path to the current directory. + * @return Returns a string containing the path to the current directory. + * If the path could not be determined, an empty string is returned. + * @remark For cutting path elements '\' is used rather than '/' elements. + */ + Common::String getCurrentDirectory(); + /** + * Changes the current directory. + * @param Directory The path to the new directory. The path can be relative. + * @return Returns true if the operation was successful, otherwise false. + * @remark For cutting path elements '\' is used rather than '/' elements. + */ + bool changeDirectory(const Common::String &directory); + /** + * Returns the absolute path to a file in the directory tree. + * @param FileName The filename of the file whose absolute path is to be determined. + * These parameters may include both relative and absolute paths. + * @return Returns an absolute path to the given file. + * @remark For cutting path elements '\' is used rather than '/' elements. + */ + Common::String getAbsolutePath(const Common::String &fileName); + /** + * Creates a BS_PackageManager::FileSearch object to search for files + * @param Filter Specifies the search string. Wildcards of '*' and '?' are allowed + * @param Path Specifies the directory that should be searched. + * @param TypeFilter A combination of flags BS_PackageManager::FT_DIRECTORY and BS_PackageManager::FT_FILE. + * These flags indicate whether to search for files, directories, or both. + * The default is BS_PackageManager::FT_DIRECTORY | BS_PackageManager::FT_FILE + * @return Specifies a pointer to a BS_PackageManager::FileSearch object, or NULL if no file was found. + * @remark Do not forget to delete the object after use. + */ + int doSearch(Common::ArchiveMemberList &list, const Common::String &filter, const Common::String &path, uint typeFilter = FT_DIRECTORY | FT_FILE); + + /** + * Determines whether a file exists + * @param FileName The filename + * @return Returns true if the file exists, otherwise false. + */ + bool fileExists(const Common::String &FileName); + +private: + bool registerScriptBindings(); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/package/packagemanager_script.cpp b/engines/sword25/package/packagemanager_script.cpp new file mode 100644 index 0000000000..9367ae3071 --- /dev/null +++ b/engines/sword25/package/packagemanager_script.cpp @@ -0,0 +1,211 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/common.h" +#include "sword25/kernel/kernel.h" +#include "sword25/script/script.h" +#include "sword25/script/luabindhelper.h" + +#include "sword25/package/packagemanager.h" + +namespace Sword25 { + +static PackageManager *getPM() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + PackageManager *pPM = pKernel->getPackage(); + BS_ASSERT(pPM); + return pPM; +} + +static int loadPackage(lua_State *L) { + PackageManager *pPM = getPM(); + + lua_pushbooleancpp(L, pPM->loadPackage(luaL_checkstring(L, 1), luaL_checkstring(L, 2))); + + return 1; +} + +static int loadDirectoryAsPackage(lua_State *L) { + PackageManager *pPM = getPM(); + + lua_pushbooleancpp(L, pPM->loadDirectoryAsPackage(luaL_checkstring(L, 1), luaL_checkstring(L, 2))); + + return 1; +} + +static int getCurrentDirectory(lua_State *L) { + PackageManager *pPM = getPM(); + + lua_pushstring(L, pPM->getCurrentDirectory().c_str()); + + return 1; +} + +static int changeDirectory(lua_State *L) { + PackageManager *pPM = getPM(); + + lua_pushbooleancpp(L, pPM->changeDirectory(luaL_checkstring(L, 1))); + + return 1; +} + +static int getAbsolutePath(lua_State *L) { + PackageManager *pPM = getPM(); + + lua_pushstring(L, pPM->getAbsolutePath(luaL_checkstring(L, 1)).c_str()); + + return 1; +} + +static int getFileSize(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushnumber(L, 0); + + return 1; +} + +static int getFileType(lua_State *L) { + // This function apparently is not used by the game scripts + lua_pushnumber(L, 0); + + return 1; +} + +static void splitSearchPath(const Common::String &path, Common::String &directory, Common::String &filter) { + // Scan backwards for a trailing slash + const char *sPath = path.c_str(); + const char *lastSlash = sPath + strlen(sPath) - 1; + while ((lastSlash >= sPath) && (*lastSlash != '/')) --lastSlash; + + if (lastSlash >= sPath) { + directory = ""; + filter = path; + } else { + directory = Common::String(sPath, lastSlash - sPath); + filter = Common::String(lastSlash + 1); + } +} + +static void doSearch(lua_State *L, const Common::String &path, uint type) { + PackageManager *pPM = getPM(); + + // Der Packagemanager-Service muss den Suchstring und den Pfad getrennt übergeben bekommen. + // Um die Benutzbarkeit zu verbessern sollen Skriptprogrammierer dieses als ein Pfad übergeben können. + // Daher muss der übergebene Pfad am letzten Slash aufgesplittet werden. + Common::String directory; + Common::String filter; + splitSearchPath(path, directory, filter); + + // Ergebnistable auf dem Lua-Stack erstellen + lua_newtable(L); + + // Suche durchführen und die Namen aller gefundenen Dateien in die Ergebnistabelle einfügen. + // Als Indizes werden fortlaufende Nummern verwandt. + uint resultNr = 1; + Common::ArchiveMemberList list; + int numMatches; + + numMatches = pPM->doSearch(list, filter, directory, type); + if (numMatches) { + for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) { + lua_pushnumber(L, resultNr); + lua_pushstring(L, (*it)->getName().c_str()); + lua_settable(L, -3); + resultNr++; + } + } +} + +static int findFiles(lua_State *L) { + doSearch(L, luaL_checkstring(L, 1), PackageManager::FT_FILE); + return 1; +} + +static int findDirectories(lua_State *L) { + doSearch(L, luaL_checkstring(L, 1), PackageManager::FT_DIRECTORY); + return 1; +} + +static int getFileAsString(lua_State *L) { + PackageManager *pPM = getPM(); + + uint fileSize; + char *fileData = (char *)pPM->getFile(luaL_checkstring(L, 1), &fileSize); + if (fileData) { + lua_pushlstring(L, fileData, fileSize); + delete[] fileData; + + return 1; + } else + return 0; +} + +static int fileExists(lua_State *L) { + lua_pushbooleancpp(L, getPM()->fileExists(luaL_checkstring(L, 1))); + return 1; +} + +static const char *PACKAGE_LIBRARY_NAME = "Package"; + +static const luaL_reg PACKAGE_FUNCTIONS[] = { + {"LoadPackage", loadPackage}, + {"LoadDirectoryAsPackage", loadDirectoryAsPackage}, + {"GetCurrentDirectory", getCurrentDirectory}, + {"ChangeDirectory", changeDirectory}, + {"GetAbsolutePath", getAbsolutePath}, + {"GetFileSize", getFileSize}, + {"GetFileType", getFileType}, + {"FindFiles", findFiles}, + {"FindDirectories", findDirectories}, + {"GetFileAsString", getFileAsString}, + {"FileExists", fileExists}, + {0, 0} +}; + +bool PackageManager::registerScriptBindings() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ScriptEngine *pScript = pKernel->getScript(); + BS_ASSERT(pScript); + lua_State *L = static_cast<lua_State *>(pScript->getScriptObject()); + BS_ASSERT(L); + + if (!LuaBindhelper::addFunctionsToLib(L, PACKAGE_LIBRARY_NAME, PACKAGE_FUNCTIONS)) + return false; + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/script/lua_extensions.cpp b/engines/sword25/script/lua_extensions.cpp new file mode 100644 index 0000000000..25a43e17d2 --- /dev/null +++ b/engines/sword25/script/lua_extensions.cpp @@ -0,0 +1,75 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/script/luascript.h" +#include "sword25/script/luabindhelper.h" + +namespace Sword25 { + +static int warning(lua_State *L) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + luaL_checkstring(L, 1); + luaL_where(L, 1); + lua_pushstring(L, "WARNING - "); + lua_pushvalue(L, 1); + lua_concat(L, 3); + BS_Log::log("%s\n", luaL_checkstring(L, -1)); + lua_pop(L, 1); + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return 0; +} + +static const luaL_reg GLOBAL_FUNCTIONS[] = { + {"warning", warning}, + {0, 0} +}; + +bool LuaScriptEngine::registerStandardLibExtensions() { + lua_State *L = _state; + BS_ASSERT(_state); + + if (!LuaBindhelper::addFunctionsToLib(L, "", GLOBAL_FUNCTIONS)) + return false; + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/script/luabindhelper.cpp b/engines/sword25/script/luabindhelper.cpp new file mode 100644 index 0000000000..5367854218 --- /dev/null +++ b/engines/sword25/script/luabindhelper.cpp @@ -0,0 +1,427 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/kernel/kernel.h" +#include "sword25/script/luabindhelper.h" +#include "sword25/script/luascript.h" + +#define BS_LOG_PREFIX "LUABINDHELPER" + +namespace { +const char *METATABLES_TABLE_NAME = "__METATABLES"; +const char *PERMANENTS_TABLE_NAME = "Permanents"; + +bool registerPermanent(lua_State *L, const Common::String &name) { + // A C function has to be on the stack + if (!lua_iscfunction(L, -1)) + return false; + + // Make sure that the Permanents-Table is on top of the stack + lua_getfield(L, LUA_REGISTRYINDEX, PERMANENTS_TABLE_NAME); + if (lua_isnil(L, -1)) { + // Permanents-Table does not yet exist, so it has to be created + + // Pop nil from the stack + lua_pop(L, 1); + + // Create Permanents-Table and insert a second reference to it on the stack + lua_newtable(L); + lua_pushvalue(L, -1); + + // Store the Permanents-Table in the registry. The second reference is left + // on the stack to be used in the connection + lua_setfield(L, LUA_REGISTRYINDEX, PERMANENTS_TABLE_NAME); + } + + // C function with the name of an index in the Permanents-Table + lua_insert(L, -2); + lua_setfield(L, -2, name.c_str()); + + // Remove the Permanents-Table from the stack + lua_pop(L, 1); + + return true; +} +} + +namespace Sword25 { + +/** + * Registers a set of functions into a Lua library. + * @param L A pointer to the Lua VM + * @param LibName The name of the library. + * If this is an empty string, the functions will be added to the global namespace. + * @param Functions An array of function pointers along with their names. + * The array must be terminated with the enry (0, 0) + * @return Returns true if successful, otherwise false. + */ +bool LuaBindhelper::addFunctionsToLib(lua_State *L, const Common::String &libName, const luaL_reg *functions) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // If the table name is empty, the functions are to be added to the global namespace + if (libName.size() == 0) { + for (; functions->name; ++functions) { + lua_pushstring(L, functions->name); + lua_pushcclosure(L, functions->func, 0); + lua_settable(L, LUA_GLOBALSINDEX); + + // Function is being permanently registed, so persistence can be ignored + lua_pushstring(L, functions->name); + lua_gettable(L, LUA_GLOBALSINDEX); + registerPermanent(L, functions->name); + } + } else { // If the table name is not empty, the functions are added to the given table + // Ensure that the library table exists + if (!createTable(L, libName)) return false; + + // Register each function into the table + for (; functions->name; ++functions) { + // Function registration + lua_pushstring(L, functions->name); + lua_pushcclosure(L, functions->func, 0); + lua_settable(L, -3); + + // Function is being permanently registed, so persistence can be ignored + lua_pushstring(L, functions->name); + lua_gettable(L, -2); + registerPermanent(L, libName + "." + functions->name); + } + + // Remove the library table from the Lua stack + lua_pop(L, 1); + } + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return true; +} + +/** + * Adds a set of constants to the Lua library + * @param L A pointer to the Lua VM + * @param LibName The name of the library. + * If this is an empty string, the functions will be added to the global namespace. + * @param Constants An array of the constant values along with their names. + * The array must be terminated with the enry (0, 0) + * @return Returns true if successful, otherwise false. + */ +bool LuaBindhelper::addConstantsToLib(lua_State *L, const Common::String &libName, const lua_constant_reg *constants) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // If the table is empty, the constants are added to the global namespace + if (libName.size() == 0) { + for (; constants->Name; ++constants) { + lua_pushstring(L, constants->Name); + lua_pushnumber(L, constants->Value); + lua_settable(L, LUA_GLOBALSINDEX); + } + } + // If the table name is nto empty, the constants are added to that table + else { + // Ensure that the library table exists + if (!createTable(L, libName)) return false; + + // Register each constant in the table + for (; constants->Name; ++constants) { + lua_pushstring(L, constants->Name); + lua_pushnumber(L, constants->Value); + lua_settable(L, -3); + } + + // Remove the library table from the Lua stack + lua_pop(L, 1); + } + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return true; +} + +/** + * Adds a set of methods to a Lua class + * @param L A pointer to the Lua VM + * @param ClassName The name of the class + * When the class name specified does not exist, it is created. + * @param Methods An array of function pointers along with their method names. + * The array must be terminated with the enry (0, 0) + * @return Returns true if successful, otherwise false. + */ +bool LuaBindhelper::addMethodsToClass(lua_State *L, const Common::String &className, const luaL_reg *methods) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // Load the metatable onto the Lua stack + if (!getMetatable(L, className)) return false; + + // Register each method in the Metatable + for (; methods->name; ++methods) { + lua_pushstring(L, methods->name); + lua_pushcclosure(L, methods->func, 0); + lua_settable(L, -3); + + // Function is being permanently registed, so persistence can be ignored + lua_pushstring(L, methods->name); + lua_gettable(L, -2); + registerPermanent(L, className + "." + methods->name); + } + + // Remove the metatable from the stack + lua_pop(L, 1); + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return true; +} + +/** + * Sets the garbage collector callback method when items of a particular class are deleted + * @param L A pointer to the Lua VM + * @param ClassName The name of the class + * When the class name specified does not exist, it is created. + * @param GCHandler A function pointer + * @return Returns true if successful, otherwise false. + */ +bool LuaBindhelper::setClassGCHandler(lua_State *L, const Common::String &className, lua_CFunction GCHandler) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(L); +#endif + + // Load the metatable onto the Lua stack + if (!getMetatable(L, className)) return false; + + // Add the GC handler to the Metatable + lua_pushstring(L, "__gc"); + lua_pushcclosure(L, GCHandler, 0); + lua_settable(L, -3); + + // Function is being permanently registed, so persistence can be ignored + lua_pushstring(L, "__gc"); + lua_gettable(L, -2); + registerPermanent(L, className + ".__gc"); + + // Remove the metatable from the stack + lua_pop(L, 1); + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(L)); +#endif + + return true; +} + +} // End of namespace Sword25 + +namespace { +void pushMetatableTable(lua_State *L) { + // Push the Metatable table onto the stack + lua_getglobal(L, METATABLES_TABLE_NAME); + + // If the table doesn't yet exist, it must be created + if (lua_isnil(L, -1)) { + // Pop nil from stack + lua_pop(L, 1); + + // New table has been created, so add it to the global table and leave reference on stack + lua_newtable(L); + lua_pushvalue(L, -1); + lua_setglobal(L, METATABLES_TABLE_NAME); + } +} +} + +namespace Sword25 { + +bool LuaBindhelper::getMetatable(lua_State *L, const Common::String &tableName) { + // Push the Metatable table onto the stack + pushMetatableTable(L); + + // Versuchen, die gewünschte Metatabelle auf den Stack zu legen. Wenn sie noch nicht existiert, muss sie erstellt werden. + lua_getfield(L, -1, tableName.c_str()); + if (lua_isnil(L, -1)) { + // Pop nil from stack + lua_pop(L, 1); + + // Create new table + lua_newtable(L); + + // Set the __index field in the table + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + + // Flag the table as persisted. This ensures that objects within this table get stored + lua_pushbooleancpp(L, true); + lua_setfield(L, -2, "__persist"); + + // Set the table name and push it onto the stack + lua_pushvalue(L, -1); + lua_setfield(L, -3, tableName.c_str()); + } + + // Remove the Metatable table from the stack + lua_remove(L, -2); + + return true; +} + +bool LuaBindhelper::createTable(lua_State *L, const Common::String &tableName) { + const char *partBegin = tableName.c_str(); + + while (partBegin - tableName.c_str() < (int)tableName.size()) { + const char *partEnd = strchr(partBegin, '.'); + if (!partEnd) + partEnd = partBegin + strlen(partBegin); + Common::String subTableName(partBegin, partEnd); + + // Tables with an empty string as the name are not allowed + if (subTableName.size() == 0) + return false; + + // Verify that the table with the name already exists + // The first round will be searched in the global namespace, with later passages + // in the corresponding parent table in the stack + if (partBegin == tableName.c_str()) { + lua_pushstring(L, subTableName.c_str()); + lua_gettable(L, LUA_GLOBALSINDEX); + } else { + lua_pushstring(L, subTableName.c_str()); + lua_gettable(L, -2); + if (!lua_isnil(L, -1)) + lua_remove(L, -2); + } + + // If it doesn't exist, create table + if (lua_isnil(L, -1)) { + // Pop nil from stack + lua_pop(L, 1); + + // Create new table + lua_newtable(L); + lua_pushstring(L, subTableName.c_str()); + lua_pushvalue(L, -2); + if (partBegin == tableName.c_str()) + lua_settable(L, LUA_GLOBALSINDEX); + else { + lua_settable(L, -4); + lua_remove(L, -2); + } + } + + partBegin = partEnd + 1; + } + + return true; +} + +} // End of namespace Sword25 + +namespace { +Common::String getLuaValueInfo(lua_State *L, int stackIndex) { + switch (lua_type(L, stackIndex)) { + case LUA_TNUMBER: + lua_pushstring(L, lua_tostring(L, stackIndex)); + break; + + case LUA_TSTRING: + lua_pushfstring(L, "\"%s\"", lua_tostring(L, stackIndex)); + break; + + case LUA_TBOOLEAN: + lua_pushstring(L, (lua_toboolean(L, stackIndex) ? "true" : "false")); + break; + + case LUA_TNIL: + lua_pushliteral(L, "nil"); + break; + + default: + lua_pushfstring(L, "%s: %p", luaL_typename(L, stackIndex), lua_topointer(L, stackIndex)); + break; + } + + Common::String result(lua_tostring(L, -1)); + lua_pop(L, 1); + + return result; +} +} + +namespace Sword25 { + +Common::String LuaBindhelper::stackDump(lua_State *L) { + Common::String oss; + + int i = lua_gettop(L); + oss += "------------------- Stack Dump -------------------\n"; + + while (i) { + oss += i + ": " + getLuaValueInfo(L, i) + "\n"; + i--; + } + + oss += "-------------- Stack Dump Finished ---------------\n"; + + return oss; +} + +Common::String LuaBindhelper::tableDump(lua_State *L) { + Common::String oss; + + oss += "------------------- Table Dump -------------------\n"; + + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + // Get the value of the current element on top of the stack, including the index + oss += getLuaValueInfo(L, -2) + " : " + getLuaValueInfo(L, -1) + "\n"; + + // Pop value from the stack. The index is then ready for the next call to lua_next() + lua_pop(L, 1); + } + + oss += "-------------- Table Dump Finished ---------------\n"; + + return oss; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/script/luabindhelper.h b/engines/sword25/script/luabindhelper.h new file mode 100644 index 0000000000..dc45104d53 --- /dev/null +++ b/engines/sword25/script/luabindhelper.h @@ -0,0 +1,119 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_LUABINDHELPER_H +#define SWORD25_LUABINDHELPER_H + +#include "sword25/kernel/common.h" + +#include "sword25/util/lua/lua.h" +#include "sword25/util/lua/lauxlib.h" + +namespace Sword25 { + +#define lua_pushbooleancpp(L, b) (lua_pushboolean(L, b ? 1 : 0)) +#define lua_tobooleancpp(L, i) (lua_toboolean(L, i) == 0 ? false : true) + +struct lua_constant_reg { + const char *Name; + lua_Number Value; +}; + +class LuaBindhelper { +public: + /** + * Registers a set of functions into a Lua library. + * @param L A pointer to the Lua VM + * @param LibName The name of the library. + * If this is an empty string, the functions will be added to the global namespace. + * @param Functions An array of function pointers along with their names. + * The array must be terminated with the enry (0, 0) + * @return Returns true if successful, otherwise false. + */ + static bool addFunctionsToLib(lua_State *L, const Common::String &libName, const luaL_reg *functions); + + /** + * Adds a set of constants to the Lua library + * @param L A pointer to the Lua VM + * @param LibName The name of the library. + * If this is an empty string, the functions will be added to the global namespace. + * @param Constants An array of the constant values along with their names. + * The array must be terminated with the enry (0, 0) + * @return Returns true if successful, otherwise false. + */ + static bool addConstantsToLib(lua_State *L, const Common::String &libName, const lua_constant_reg *constants); + + /** + * Adds a set of methods to a Lua class + * @param L A pointer to the Lua VM + * @param ClassName The name of the class + * When the class name specified does not exist, it is created. + * @param Methods An array of function pointers along with their method names. + * The array must be terminated with the enry (0, 0) + * @return Returns true if successful, otherwise false. + */ + static bool addMethodsToClass(lua_State *L, const Common::String &className, const luaL_reg *methods); + + /** + * Sets the garbage collector callback method when items of a particular class are deleted + * @param L A pointer to the Lua VM + * @param ClassName The name of the class + * When the class name specified does not exist, it is created. + * @param GCHandler A function pointer + * @return Returns true if successful, otherwise false. + */ + static bool setClassGCHandler(lua_State *L, const Common::String &className, lua_CFunction GCHandler); + + /** + * Returns a string containing a stack dump of the Lua stack + * @param L A pointer to the Lua VM + */ + static Common::String stackDump(lua_State *L); + + /** + * Returns a string that describes the contents of a table + * @param L A pointer to the Lua VM + * @remark The table must be on the Lua stack to be read out. + */ + static Common::String tableDump(lua_State *L); + + static bool getMetatable(lua_State *L, const Common::String &tableName); + +private: + static bool createTable(lua_State *L, const Common::String &tableName); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/script/luacallback.cpp b/engines/sword25/script/luacallback.cpp new file mode 100644 index 0000000000..bb2c821aa8 --- /dev/null +++ b/engines/sword25/script/luacallback.cpp @@ -0,0 +1,173 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "sword25/script/luacallback.h" +#include "sword25/script/luabindhelper.h" + +#include "sword25/util/lua/lua.h" +#include "sword25/util/lua/lauxlib.h" + +const char *CALLBACKTABLE_NAME = "__CALLBACKS"; + +namespace Sword25 { + +#define BS_LOG_PREFIX "LUA" + +LuaCallback::LuaCallback(lua_State *L) { + // Create callback table + lua_newtable(L); + lua_setglobal(L, CALLBACKTABLE_NAME); +} + +LuaCallback::~LuaCallback() { +} + +void LuaCallback::registerCallbackFunction(lua_State *L, uint objectHandle) { + BS_ASSERT(lua_isfunction(L, -1)); + ensureObjectCallbackTableExists(L, objectHandle); + + // Store function in the callback object table store + lua_pushvalue(L, -2); + luaL_ref(L, -2); + + // Pop the function and object callback table from the stack + lua_pop(L, 2); +} + +void LuaCallback::unregisterCallbackFunction(lua_State *L, uint objectHandle) { + BS_ASSERT(lua_isfunction(L, -1)); + ensureObjectCallbackTableExists(L, objectHandle); + + // Iterate over all elements of the object callback table and remove the function from it + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + // The value of the current element is the top of the stack, including the index + + // If the value is identical to the function parameters, it is removed from the table + if (lua_equal(L, -1, -4)) { + lua_pushvalue(L, -2); + lua_pushnil(L); + lua_settable(L, -5); + + // The function was found, iteration can be stopped + lua_pop(L, 2); + break; + } else { + // Pop value from the stack. The index is then ready for the next call to lua_next() + lua_pop(L, 1); + } + } + + // Function and object table are popped from the stack + lua_pop(L, 2); +} + +void LuaCallback::removeAllObjectCallbacks(lua_State *L, uint objectHandle) { + pushCallbackTable(L); + + // Remove the object callback from the callback table + lua_pushnumber(L, objectHandle); + lua_pushnil(L); + lua_settable(L, -3); + + lua_pop(L, 1); +} + +void LuaCallback::invokeCallbackFunctions(lua_State *L, uint objectHandle) { + ensureObjectCallbackTableExists(L, objectHandle); + + // Iterate through the table and perform all the callbacks + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + // The value of the current element is at the top of the stack, including the index + + // If the value is a function, execute it + if (lua_type(L, -1) == LUA_TFUNCTION) { + // Pre-Function Call + // Derived classes can function in this parameter onto the stack. + // The return value indicates the number of parameters + int argumentCount = preFunctionInvokation(L); + + // Lua_pcall the function and the parameters pop themselves from the stack + if (lua_pcall(L, argumentCount, 0, 0) != 0) { + // An error has occurred + BS_LOG_ERRORLN("An error occured executing a callback function: %s", lua_tostring(L, -1)); + + // Pop error message from the stack + lua_pop(L, 1); + } + } else { + // Pop value from the stack. The index is then ready for the next call to lua_next() + lua_pop(L, 1); + } + } +} + +void LuaCallback::ensureObjectCallbackTableExists(lua_State *L, uint objectHandle) { + pushObjectCallbackTable(L, objectHandle); + + // If the table is nil, it must first be created + if (lua_isnil(L, -1)) { + // Pop nil from stack + lua_pop(L, 1); + + pushCallbackTable(L); + + // Create the table, and put the objectHandle into it + lua_newtable(L); + lua_pushnumber(L, objectHandle); + lua_pushvalue(L, -2); + lua_settable(L, -4); + + // Pop the callback table from the stack + lua_remove(L, -2); + } +} + +void LuaCallback::pushCallbackTable(lua_State *L) { + lua_getglobal(L, CALLBACKTABLE_NAME); +} + +void LuaCallback::pushObjectCallbackTable(lua_State *L, uint objectHandle) { + pushCallbackTable(L); + + // Push Object Callback table onto the stack + lua_pushnumber(L, objectHandle); + lua_gettable(L, -2); + + // Pop the callback table from the stack + lua_remove(L, -2); +} + +} // End of namespace Sword25 diff --git a/engines/sword25/script/luacallback.h b/engines/sword25/script/luacallback.h new file mode 100644 index 0000000000..e097f5b499 --- /dev/null +++ b/engines/sword25/script/luacallback.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_LUACALLBACK_H +#define SWORD25_LUACALLBACK_H + +#include "sword25/kernel/common.h" + +struct lua_State; + +namespace Sword25 { + +class LuaCallback { +public: + LuaCallback(lua_State *L); + virtual ~LuaCallback(); + + // Funktion muss auf dem Lua-Stack liegen. + void registerCallbackFunction(lua_State *L, uint objectHandle); + + // Funktion muss auf dem Lua-Stack liegen. + void unregisterCallbackFunction(lua_State *L, uint objectHandle); + + void removeAllObjectCallbacks(lua_State *L, uint objectHandle); + + void invokeCallbackFunctions(lua_State *L, uint objectHandle); + +protected: + virtual int preFunctionInvokation(lua_State *L) { + return 0; + } + +private: + void ensureObjectCallbackTableExists(lua_State *L, uint objectHandle); + void pushCallbackTable(lua_State *L); + void pushObjectCallbackTable(lua_State *L, uint objectHandle); +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/script/luascript.cpp b/engines/sword25/script/luascript.cpp new file mode 100644 index 0000000000..aa2bdb9e06 --- /dev/null +++ b/engines/sword25/script/luascript.cpp @@ -0,0 +1,557 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "LUA" + +#include "common/array.h" +#include "common/debug-channels.h" + +#include "sword25/sword25.h" +#include "sword25/package/packagemanager.h" +#include "sword25/script/luascript.h" +#include "sword25/script/luabindhelper.h" + +#include "sword25/kernel/outputpersistenceblock.h" +#include "sword25/kernel/inputpersistenceblock.h" + +#include "sword25/util/lua/lua.h" +#include "sword25/util/lua/lualib.h" +#include "sword25/util/lua/lauxlib.h" +#include "sword25/util/pluto/pluto.h" + +namespace Sword25 { + +LuaScriptEngine::LuaScriptEngine(Kernel *KernelPtr) : + ScriptEngine(KernelPtr), + _state(0), + _pcallErrorhandlerRegistryIndex(0) { +} + +LuaScriptEngine::~LuaScriptEngine() { + // Lua de-initialisation + if (_state) + lua_close(_state); +} + +namespace { +int panicCB(lua_State *L) { + BS_LOG_ERRORLN("Lua panic. Error message: %s", lua_isnil(L, -1) ? "" : lua_tostring(L, -1)); + return 0; +} + +void debugHook(lua_State *L, lua_Debug *ar) { + if (!lua_getinfo(L, "Sn", ar)) + return; + + debug("LUA: %s %s: %s %d", ar->namewhat, ar->name, ar->short_src, ar->currentline); +} +} + +bool LuaScriptEngine::init() { + // Lua-State initialisation, as well as standard libaries initialisation + _state = luaL_newstate(); + if (!_state || ! registerStandardLibs() || !registerStandardLibExtensions()) { + BS_LOG_ERRORLN("Lua could not be initialized."); + return false; + } + + // Register panic callback function + lua_atpanic(_state, panicCB); + + // Error handler for lua_pcall calls + // The code below contains a local error handler function + const char errorHandlerCode[] = + "local function ErrorHandler(message) " + " return message .. '\\n' .. debug.traceback('', 2) " + "end " + "return ErrorHandler"; + + // Compile the code + if (luaL_loadbuffer(_state, errorHandlerCode, strlen(errorHandlerCode), "PCALL ERRORHANDLER") != 0) { + // An error occurred, so dislay the reason and exit + BS_LOG_ERRORLN("Couldn't compile luaL_pcall errorhandler:\n%s", lua_tostring(_state, -1)); + lua_pop(_state, 1); + + return false; + } + // Running the code, the error handler function sets the top of the stack + if (lua_pcall(_state, 0, 1, 0) != 0) { + // An error occurred, so dislay the reason and exit + BS_LOG_ERRORLN("Couldn't prepare luaL_pcall errorhandler:\n%s", lua_tostring(_state, -1)); + lua_pop(_state, 1); + + return false; + } + + // Place the error handler function in the Lua registry, and remember the index + _pcallErrorhandlerRegistryIndex = luaL_ref(_state, LUA_REGISTRYINDEX); + + // Initialise the Pluto-Persistence library + luaopen_pluto(_state); + lua_pop(_state, 1); + + // Initialize debugging callback + if (DebugMan.isDebugChannelEnabled(kDebugScript)) { + int mask = 0; + if ((gDebugLevel & 1) != 0) + mask |= LUA_MASKCALL; + if ((gDebugLevel & 2) != 0) + mask |= LUA_MASKRET; + if ((gDebugLevel & 4) != 0) + mask |= LUA_MASKLINE; + + if (mask != 0) + lua_sethook(_state, debugHook, mask, 0); + } + + BS_LOGLN("Lua initialized."); + + return true; +} + +bool LuaScriptEngine::executeFile(const Common::String &fileName) { +#ifdef DEBUG + int __startStackDepth = lua_gettop(_state); +#endif + debug(2, "LuaScriptEngine::executeFile(%s)", fileName.c_str()); + + // Get a pointer to the package manager + PackageManager *pPackage = Kernel::getInstance()->getPackage(); + BS_ASSERT(pPackage); + + // File read + uint fileSize; + byte *fileData = pPackage->getFile(fileName, &fileSize); + if (!fileData) { + BS_LOG_ERRORLN("Couldn't read \"%s\".", fileName.c_str()); +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(_state)); +#endif + return false; + } + + // Run the file content + if (!executeBuffer(fileData, fileSize, "@" + pPackage->getAbsolutePath(fileName))) { + // Release file buffer + delete[] fileData; +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(_state)); +#endif + return false; + } + + // Release file buffer + delete[] fileData; + +#ifdef DEBUG + BS_ASSERT(__startStackDepth == lua_gettop(_state)); +#endif + + return true; +} + +bool LuaScriptEngine::executeString(const Common::String &code) { + return executeBuffer((byte *)code.c_str(), code.size(), "???"); +} + +namespace { + +void removeForbiddenFunctions(lua_State *L) { + static const char *FORBIDDEN_FUNCTIONS[] = { + "dofile", + 0 + }; + + const char **iterator = FORBIDDEN_FUNCTIONS; + while (*iterator) { + lua_pushnil(L); + lua_setfield(L, LUA_GLOBALSINDEX, *iterator); + ++iterator; + } +} +} + +bool LuaScriptEngine::registerStandardLibs() { + luaL_openlibs(_state); + removeForbiddenFunctions(_state); + return true; +} + +bool LuaScriptEngine::executeBuffer(const byte *data, uint size, const Common::String &name) const { + // Compile buffer + if (luaL_loadbuffer(_state, (const char *)data, size, name.c_str()) != 0) { + BS_LOG_ERRORLN("Couldn't compile \"%s\":\n%s", name.c_str(), lua_tostring(_state, -1)); + lua_pop(_state, 1); + + return false; + } + + // Error handling function to be executed after the function is put on the stack + lua_rawgeti(_state, LUA_REGISTRYINDEX, _pcallErrorhandlerRegistryIndex); + lua_insert(_state, -2); + + // Run buffer contents + if (lua_pcall(_state, 0, 0, -2) != 0) { + BS_LOG_ERRORLN("An error occured while executing \"%s\":\n%s.", + name.c_str(), + lua_tostring(_state, -1)); + lua_pop(_state, 2); + + return false; + } + + // Remove the error handler function from the stack + lua_pop(_state, 1); + + return true; +} + +void LuaScriptEngine::setCommandLine(const Common::StringArray &commandLineParameters) { + lua_newtable(_state); + + for (size_t i = 0; i < commandLineParameters.size(); ++i) { + lua_pushnumber(_state, i + 1); + lua_pushstring(_state, commandLineParameters[i].c_str()); + lua_settable(_state, -3); + } + + lua_setglobal(_state, "CommandLine"); +} + +namespace { +const char *PERMANENTS_TABLE_NAME = "Permanents"; + +// This array contains the name of global Lua objects that should not be persisted +const char *STANDARD_PERMANENTS[] = { + "string", + "xpcall", + "package", + "tostring", + "print", + "os", + "unpack", + "require", + "getfenv", + "setmetatable", + "next", + "assert", + "tonumber", + "io", + "rawequal", + "collectgarbage", + "getmetatable", + "module", + "rawset", + "warning", + "math", + "debug", + "pcall", + "table", + "newproxy", + "type", + "coroutine", + "select", + "gcinfo", + "pairs", + "rawget", + "loadstring", + "ipairs", + "_VERSION", + "setfenv", + "load", + "error", + "loadfile", + + "pairs_next", + "ipairs_next", + "pluto", + "Cfg", + "Translator", + "Persistence", + "CommandLine", + 0 +}; + +enum PERMANENT_TABLE_TYPE { + PTT_PERSIST, + PTT_UNPERSIST +}; + +bool pushPermanentsTable(lua_State *L, PERMANENT_TABLE_TYPE tableType) { + // Permanents-Table + lua_newtable(L); + + // All standard permanents are inserted into this table + uint Index = 0; + while (STANDARD_PERMANENTS[Index]) { + // Permanents are placed onto the stack; if it does not exist, it is simply ignored + lua_getglobal(L, STANDARD_PERMANENTS[Index]); + if (!lua_isnil(L, -1)) { + // Name of the element as a unique value on the stack + lua_pushstring(L, STANDARD_PERMANENTS[Index]); + + // If it is loaded, then it can be used + // In this case, the position of name and object are reversed on the stack + if (tableType == PTT_UNPERSIST) + lua_insert(L, -2); + + // Make an entry in the table + lua_settable(L, -3); + } else { + // Pop nil value from stack + lua_pop(L, 1); + } + + ++Index; + } + + // All registered C functions to be inserted into the table + // BS_LuaBindhelper places in the register a table in which all registered C functions + // are stored + + // Table is put on the stack + lua_getfield(L, LUA_REGISTRYINDEX, PERMANENTS_TABLE_NAME); + + if (!lua_isnil(L, -1)) { + // Iterate over all elements of the table + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + // Value and index duplicated on the stack and changed in the sequence + lua_pushvalue(L, -1); + lua_pushvalue(L, -3); + + // If it is loaded, then it can be used + // In this case, the position of name and object are reversed on the stack + if (tableType == PTT_UNPERSIST) + lua_insert(L, -2); + + // Make an entry in the results table + lua_settable(L, -6); + + // Pop value from the stack. The index is then ready for the next call to lua_next() + lua_pop(L, 1); + } + } + + // Pop the C-Permanents table from the stack + lua_pop(L, 1); + + // coroutine.yield must be registered in the extra-Permanents table because they + // are inactive coroutine C functions on the stack + + // Function coroutine.yield placed on the stack + lua_getglobal(L, "coroutine"); + lua_pushstring(L, "yield"); + lua_gettable(L, -2); + + // Store coroutine.yield with it's own unique value in the Permanents table + lua_pushstring(L, "coroutine.yield"); + + if (tableType == PTT_UNPERSIST) + lua_insert(L, -2); + + lua_settable(L, -4); + + // Coroutine table is popped from the stack + lua_pop(L, 1); + + return true; +} +} + +namespace { +int chunkwriter(lua_State *L, const void *p, size_t sz, void *ud) { + Common::Array<byte> & chunkData = *reinterpret_cast<Common::Array<byte> * >(ud); + const byte *buffer = reinterpret_cast<const byte *>(p); + + while (sz--) + chunkData.push_back(*buffer++); + + return 1; +} +} + +bool LuaScriptEngine::persist(OutputPersistenceBlock &writer) { + // Empty the Lua stack. pluto_persist() xepects that the stack is empty except for its parameters + lua_settop(_state, 0); + + // Garbage Collection erzwingen. + lua_gc(_state, LUA_GCCOLLECT, 0); + + // Permanents-Table is set on the stack + // pluto_persist expects these two items on the Lua stack + pushPermanentsTable(_state, PTT_PERSIST); + lua_getglobal(_state, "_G"); + + // Lua persists and stores the data in a Common::Array + Common::Array<byte> chunkData; + pluto_persist(_state, chunkwriter, &chunkData); + + // Persistenzdaten in den Writer schreiben. + writer.writeByteArray(chunkData); + + // Die beiden Tabellen vom Stack nehmen. + lua_pop(_state, 2); + + return true; +} + +namespace { + +struct ChunkreaderData { + void *BufferPtr; + size_t Size; + bool BufferReturned; +}; + +const char *chunkreader(lua_State *L, void *ud, size_t *sz) { + ChunkreaderData &cd = *reinterpret_cast<ChunkreaderData *>(ud); + + if (!cd.BufferReturned) { + cd.BufferReturned = true; + *sz = cd.Size; + return reinterpret_cast<const char *>(cd.BufferPtr); + } else { + return 0; + } +} + +void clearGlobalTable(lua_State *L, const char **exceptions) { + // Iterate over all elements of the global table + lua_pushvalue(L, LUA_GLOBALSINDEX); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + // Now the value and the index of the current element is on the stack + // This value does not interest us, so it is popped from the stack + lua_pop(L, 1); + + // Determine whether the item is set to nil, so you want to remove from the global table. + // For this will determine whether the element name is a string and is present in + // the list of exceptions + bool setElementToNil = true; + if (lua_isstring(L, -1)) { + const char *indexString = lua_tostring(L, -1); + const char **exceptionsWalker = exceptions; + while (*exceptionsWalker) { + if (strcmp(indexString, *exceptionsWalker) == 0) + setElementToNil = false; + ++exceptionsWalker; + } + } + + // If the above test showed that the item should be removed, it is removed by setting the value to nil. + if (setElementToNil) { + lua_pushvalue(L, -1); + lua_pushnil(L); + lua_settable(L, LUA_GLOBALSINDEX); + } + } + + // Pop the Global table from the stack + lua_pop(L, 1); + + // Perform garbage collection, so that all removed elements are deleted + lua_gc(L, LUA_GCCOLLECT, 0); +} +} + +bool LuaScriptEngine::unpersist(InputPersistenceBlock &reader) { + // Empty the Lua stack. pluto_persist() xepects that the stack is empty except for its parameters + lua_settop(_state, 0); + + // Permanents table is placed on the stack. This has already happened at this point, because + // to create the table all permanents must be accessible. This is the case only for the + // beginning of the function, because the global table is emptied below + pushPermanentsTable(_state, PTT_UNPERSIST); + + // All items from global table of _G and __METATABLES are removed. + // After a garbage collection is performed, and thus all managed objects deleted + + // __METATABLES is not immediately removed becausen the Metatables are needed + // for the finalisers of objects. + static const char *clearExceptionsFirstPass[] = { + "_G", + "__METATABLES", + 0 + }; + clearGlobalTable(_state, clearExceptionsFirstPass); + + // In the second pass, the Metatables are removed + static const char *clearExceptionsSecondPass[] = { + "_G", + 0 + }; + clearGlobalTable(_state, clearExceptionsSecondPass); + + // Persisted Lua data + Common::Array<byte> chunkData; + reader.readByteArray(chunkData); + + // Chunk-Reader initialisation. It is used with pluto_unpersist to restore read data + ChunkreaderData cd; + cd.BufferPtr = &chunkData[0]; + cd.Size = chunkData.size(); + cd.BufferReturned = false; + + pluto_unpersist(_state, chunkreader, &cd); + + // Permanents-Table is removed from stack + lua_remove(_state, -2); + + // The read elements in the global table about + lua_pushnil(_state); + while (lua_next(_state, -2) != 0) { + // The referenec to the global table (_G) must not be overwritten, or ticks from Lua total + bool isGlobalReference = lua_isstring(_state, -2) && strcmp(lua_tostring(_state, -2), "_G") == 0; + if (!isGlobalReference) { + lua_pushvalue(_state, -2); + lua_pushvalue(_state, -2); + + lua_settable(_state, LUA_GLOBALSINDEX); + } + + // Pop value from the stack. The index is then ready for the next call to lua_next() + lua_pop(_state, 1); + } + + // The table with the loaded data is popped from the stack + lua_pop(_state, 1); + + // Force garbage collection + lua_gc(_state, LUA_GCCOLLECT, 0); + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/script/luascript.h b/engines/sword25/script/luascript.h new file mode 100644 index 0000000000..b66c32176a --- /dev/null +++ b/engines/sword25/script/luascript.h @@ -0,0 +1,109 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_LUASCRIPT_H +#define SWORD25_LUASCRIPT_H + +#include "common/str.h" +#include "common/str-array.h" +#include "sword25/kernel/common.h" +#include "sword25/script/script.h" +#include "sword25/util/lua/lua.h" + +namespace Sword25 { + +class Kernel; + +class LuaScriptEngine : public ScriptEngine { +public: + LuaScriptEngine(Kernel *KernelPtr); + virtual ~LuaScriptEngine(); + + /** + * Initialises the scripting engine + * @return Returns true if successful, otherwise false. + */ + virtual bool init(); + + /** + * Loads a script file and executes it + * @param FileName The filename of the script + * @return Returns true if successful, otherwise false. + */ + virtual bool executeFile(const Common::String &fileName); + + /** + * Execute a string of script code + * @param Code A string of script code + * @return Returns true if successful, otherwise false. + */ + virtual bool executeString(const Common::String &code); + + /** + * Returns a pointer to the main object of the scripting language + * @remark Using this method breaks the encapsulation of the language + */ + virtual void *getScriptObject() { + return _state; + } + + /** + * Makes the command line parameters for the scripting environment available + * @param CommandLineParameters An array containing all the command line parameters + * @remark How the command line parameters will be used by scripts is + * dependant on the particular implementation. + */ + virtual void setCommandLine(const Common::StringArray &commandLineParameters); + + /** + * @remark The Lua stack is cleared by this method + */ + virtual bool persist(OutputPersistenceBlock &writer); + /** + * @remark The Lua stack is cleared by this method + */ + virtual bool unpersist(InputPersistenceBlock &reader); + +private: + lua_State *_state; + int _pcallErrorhandlerRegistryIndex; + + bool registerStandardLibs(); + bool registerStandardLibExtensions(); + bool executeBuffer(const byte *data, uint size, const Common::String &name) const; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/script/script.h b/engines/sword25/script/script.h new file mode 100644 index 0000000000..9ca146026e --- /dev/null +++ b/engines/sword25/script/script.h @@ -0,0 +1,96 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#ifndef SWORD25_SCRIPT_H +#define SWORD25_SCRIPT_H + +#include "common/array.h" +#include "common/str.h" +#include "sword25/kernel/common.h" +#include "sword25/kernel/service.h" +#include "sword25/kernel/persistable.h" + +namespace Sword25 { + +class Kernel; +class OutputPersistenceBlock; +class BS_InputPersistenceBlock; + +class ScriptEngine : public Service, public Persistable { +public: + ScriptEngine(Kernel *KernelPtr) : Service(KernelPtr) {} + virtual ~ScriptEngine() {} + + // ----------------------------------------------------------------------------- + // This method must be implemented by the script engine + // ----------------------------------------------------------------------------- + + /** + * Initialises the scrip tengine. Returns true if successful, false otherwise. + */ + virtual bool init() = 0; + + /** + * Loads a script file and executes it. + * @param FileName The script filename + */ + virtual bool executeFile(const Common::String &fileName) = 0; + + /** + * Executes a specified script fragment + * @param Code String of script code + */ + virtual bool executeString(const Common::String &code) = 0; + + /** + * Returns a pointer to the main object of the script engine + * Note: Using this method breaks the encapsulation of the language from the rest of the engine. + */ + virtual void *getScriptObject() = 0; + + /** + * Makes the command line parameters for the script environment available + * Note: How the command line parameters will be used by scripts is dependant on the + * particular implementation. + * @param CommandLineParameters List containing the command line parameters + */ + virtual void setCommandLine(const Common::Array<Common::String> &commandLineParameters) = 0; + + virtual bool persist(OutputPersistenceBlock &writer) = 0; + virtual bool unpersist(InputPersistenceBlock &reader) = 0; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/sfx/soundengine.cpp b/engines/sword25/sfx/soundengine.cpp new file mode 100644 index 0000000000..fa39639f51 --- /dev/null +++ b/engines/sword25/sfx/soundengine.cpp @@ -0,0 +1,270 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#define BS_LOG_PREFIX "SOUNDENGINE" + +#include "sword25/sword25.h" +#include "sword25/sfx/soundengine.h" +#include "sword25/package/packagemanager.h" +#include "sword25/kernel/resource.h" + +#include "sound/decoders/vorbis.h" + +namespace Sword25 { + +class SoundResource : public Resource { +public: + SoundResource(const Common::String &fileName) : Resource(fileName, Resource::TYPE_SOUND), _fname(fileName) {} + virtual ~SoundResource() { + debugC(1, kDebugSound, "SoundResource: Unloading file %s", _fname.c_str()); + } + +private: + Common::String _fname; +}; + + +SoundEngine::SoundEngine(Kernel *pKernel) : ResourceService(pKernel) { + if (!registerScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); + + _mixer = g_system->getMixer(); + + for (int i = 0; i < SOUND_HANDLES; i++) + _handles[i].type = kFreeHandle; +} + +bool SoundEngine::init(uint sampleRate, uint channels) { + warning("STUB: SoundEngine::init(%d, %d)", sampleRate, channels); + + return true; +} + +void SoundEngine::update() { +} + +void SoundEngine::setVolume(float volume, SOUND_TYPES type) { + warning("STUB: SoundEngine::setVolume(%f, %d)", volume, type); +} + +float SoundEngine::getVolume(SOUND_TYPES type) { + warning("STUB: SoundEngine::getVolume(%d)", type); + return 0; +} + +void SoundEngine::pauseAll() { + debugC(1, kDebugSound, "SoundEngine::pauseAll()"); + + _mixer->pauseAll(true); +} + +void SoundEngine::resumeAll() { + debugC(1, kDebugSound, "SoundEngine::resumeAll()"); + + _mixer->pauseAll(false); +} + +void SoundEngine::pauseLayer(uint layer) { + warning("STUB: SoundEngine::pauseLayer(%d)", layer); +} + +void SoundEngine::resumeLayer(uint layer) { + warning("STUB: SoundEngine::resumeLayer(%d)", layer); +} + +SndHandle *SoundEngine::getHandle(uint *id) { + + // NOTE: Index 0 means error. Thus we're not using it + for (uint i = 1; i < SOUND_HANDLES; i++) { + if (_handles[i].type != kFreeHandle && !_mixer->isSoundHandleActive(_handles[i].handle)) { + debugC(kDebugSound, 5, "Handle %d has finished playing", i); + _handles[i].type = kFreeHandle; + } + } + + for (uint i = 1; i < SOUND_HANDLES; i++) { + if (_handles[i].type == kFreeHandle) { + debugC(kDebugSound, 5, "Allocated handle %d", i); + if (id) + *id = i; + return &_handles[i]; + } + } + + error("Sound::getHandle(): Too many sound handles"); + + return NULL; +} + +Audio::Mixer::SoundType getType(SoundEngine::SOUND_TYPES type) { + switch (type) { + case SoundEngine::MUSIC: + return Audio::Mixer::kMusicSoundType; + case SoundEngine::SPEECH: + return Audio::Mixer::kSpeechSoundType; + case SoundEngine::SFX: + return Audio::Mixer::kSFXSoundType; + default: + error("Unknown SOUND_TYPE"); + } + + return Audio::Mixer::kPlainSoundType; +} + +bool SoundEngine::playSound(const Common::String &fileName, SOUND_TYPES type, float volume, float pan, bool loop, int loopStart, int loopEnd, uint layer) { + debugC(1, kDebugSound, "SoundEngine::playSound(%s, %d, %f, %f, %d, %d, %d, %d)", fileName.c_str(), type, volume, pan, loop, loopStart, loopEnd, layer); + + playSoundEx(fileName, type, volume, pan, loop, loopStart, loopEnd, layer); + + return true; +} + +uint SoundEngine::playSoundEx(const Common::String &fileName, SOUND_TYPES type, float volume, float pan, bool loop, int loopStart, int loopEnd, uint layer) { + Common::SeekableReadStream *in = Kernel::getInstance()->getPackage()->getStream(fileName); + Audio::SeekableAudioStream *stream = Audio::makeVorbisStream(in, DisposeAfterUse::YES); + uint id; + SndHandle *handle = getHandle(&id); + + debugC(1, kDebugSound, "SoundEngine::playSoundEx(%s, %d, %f, %f, %d, %d, %d, %d)", fileName.c_str(), type, volume, pan, loop, loopStart, loopEnd, layer); + + _mixer->playStream(getType(type), &(handle->handle), stream, -1, (byte)(volume * 255), (int8)(pan * 127)); + + return id; +} + +void SoundEngine::setSoundVolume(uint handle, float volume) { + assert(handle < SOUND_HANDLES); + + debugC(1, kDebugSound, "SoundEngine::setSoundVolume(%d, %f)", handle, volume); + + _mixer->setChannelVolume(_handles[handle].handle, (byte)(volume * 255)); +} + +void SoundEngine::setSoundPanning(uint handle, float pan) { + assert(handle < SOUND_HANDLES); + + debugC(1, kDebugSound, "SoundEngine::setSoundPanning(%d, %f)", handle, pan); + + _mixer->setChannelBalance(_handles[handle].handle, (int8)(pan * 127)); +} + +void SoundEngine::pauseSound(uint handle) { + assert(handle < SOUND_HANDLES); + + debugC(1, kDebugSound, "SoundEngine::pauseSound(%d)", handle); + + _mixer->pauseHandle(_handles[handle].handle, true); +} + +void SoundEngine::resumeSound(uint handle) { + assert(handle < SOUND_HANDLES); + + debugC(1, kDebugSound, "SoundEngine::resumeSound(%d)", handle); + + _mixer->pauseHandle(_handles[handle].handle, false); +} + +void SoundEngine::stopSound(uint handle) { + assert(handle < SOUND_HANDLES); + + debugC(1, kDebugSound, "SoundEngine::stopSound(%d)", handle); + + _mixer->stopHandle(_handles[handle].handle); +} + +bool SoundEngine::isSoundPaused(uint handle) { + warning("STUB: SoundEngine::isSoundPaused(%d)", handle); + + return false; +} + +bool SoundEngine::isSoundPlaying(uint handle) { + assert(handle < SOUND_HANDLES); + + debugC(1, kDebugSound, "SoundEngine::isSoundPlaying(%d)", handle); + + return _mixer->isSoundHandleActive(_handles[handle].handle); +} + +float SoundEngine::getSoundVolume(uint handle) { + warning("STUB: SoundEngine::getSoundVolume(%d)", handle); + + return 0; +} + +float SoundEngine::getSoundPanning(uint handle) { + warning("STUB: SoundEngine::getSoundPanning(%d)", handle); + + return 0; +} + +float SoundEngine::getSoundTime(uint handle) { + warning("STUB: SoundEngine::getSoundTime(%d)", handle); + + return 0; +} + +Resource *SoundEngine::loadResource(const Common::String &fileName) { + warning("STUB: SoundEngine::loadResource(%s)", fileName.c_str()); + + return new SoundResource(fileName); +} + +bool SoundEngine::canLoadResource(const Common::String &fileName) { + Common::String fname = fileName; + + debugC(1, kDebugSound, "SoundEngine::canLoadResource(%s)", fileName.c_str()); + + fname.toLowercase(); + + return fname.hasSuffix(".ogg"); +} + + +bool SoundEngine::persist(OutputPersistenceBlock &writer) { + warning("STUB: SoundEngine::persist()"); + + return true; +} + +bool SoundEngine::unpersist(InputPersistenceBlock &reader) { + warning("STUB: SoundEngine::unpersist()"); + + return true; +} + + +} // End of namespace Sword25 diff --git a/engines/sword25/sfx/soundengine.h b/engines/sword25/sfx/soundengine.h new file mode 100644 index 0000000000..b8c10cc293 --- /dev/null +++ b/engines/sword25/sfx/soundengine.h @@ -0,0 +1,263 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +/* + * BS_SoundEngine + * ------------- + * This is the sound engine interface that contains all the methods a sound + * engine must implement. + * Implementations of the sound engine have to be derived from this class. + * It should be noted that a sound engine must maintain a list of all the + * samples it uses, and that these samples should be released when the + * destructor is called. + * + * Autor: Malte Thiesen + */ + +#ifndef SWORD25_SOUNDENGINE_H +#define SWORD25_SOUNDENGINE_H + +#include "sword25/kernel/common.h" +#include "sword25/kernel/resservice.h" +#include "sword25/kernel/persistable.h" + +#include "sound/audiostream.h" +#include "sound/mixer.h" + +namespace Sword25 { + +#define SOUND_HANDLES 32 + +enum sndHandleType { + kFreeHandle, + kEffectHandle, + kVoiceHandle +}; + +struct SndHandle { + Audio::SoundHandle handle; + sndHandleType type; +}; + + +class SoundEngine : public ResourceService, public Persistable { +public: + enum SOUND_TYPES { + MUSIC = 0, + SPEECH = 1, + SFX = 2 + }; + + /** + * The callback function of PlayDynamicSoundEx + * @param UserData User-specified pointer + * @param Data Pointer to the data buffer + * @param DataLength Length of the data to be written in bytes + */ + typedef void (*DynamicSoundReadCallback)(void *UserData, void *Data, uint DataLength); + + SoundEngine(Kernel *pKernel); + ~SoundEngine() {} + + /** + * Initialises the sound engine + * @param SampleRate Specifies the sample rate to use. + * @param Channels The maximum number of channels. The default is 32. + * @return Returns true on success, otherwise false. + * @remark Calls to other methods may take place only if this + * method was called successfully. + */ + bool init(uint sampleRate, uint channels = 32); + + /** + * Performs a "tick" of the sound engine + * + * This method should be called once per frame. It can be used by implementations + * of the sound engine that are not running in their own thread, or to perform + * additional administrative tasks that are needed. + */ + void update(); + + /** + * Sets the default volume for the different sound types + * @param Volume The default volume level (0 = off, 1 = full volume) + * @param Type The SoundType whose volume is to be changed + */ + void setVolume(float volume, SOUND_TYPES type); + + /** + * Specifies the default volume of different sound types + * @param Type The SoundType + * @return Returns the standard sound volume for the given type + * (0 = off, 1 = full volume). + */ + float getVolume(SOUND_TYPES type); + + /** + * Pauses all the sounds that are playing. + */ + void pauseAll(); + + /** + * Resumes all currently stopped sounds + */ + void resumeAll(); + + /** + * Pauses all sounds of a given layer. + * @param Layer The Sound Layer + */ + void pauseLayer(uint layer); + + /** + * Resumes all the sounds in a layer that was previously stopped with PauseLayer() + * @param Layer The Sound Layer + */ + void resumeLayer(uint layer); + + /** + * Plays a sound + * @param FileName The filename of the sound to be played + * @param Type The type of sound + * @param Volume The volume of the sound (0 = off, 1 = full volume) + * @param Pan Panning (-1 = full left, 1 = right) + * @param Loop Indicates whether the sound should be looped + * @param LoopStart Indicates the starting loop point. If a value less than 0 is passed, the start + * of the sound is used. + * @param LoopEnd Indicates the ending loop point. If a avlue is passed less than 0, the end of + * the sound is used. + * @param Layer The sound layer + * @return Returns true if the playback of the sound was started successfully. + * @remark If more control is needed over the playing, eg. changing the sound parameters + * for Volume and Panning, then PlaySoundEx should be used. + */ + bool playSound(const Common::String &fileName, SOUND_TYPES type, float volume = 1.0f, float pan = 0.0f, bool loop = false, int loopStart = -1, int loopEnd = -1, uint layer = 0); + + /** + * Plays a sound + * @param Type The type of sound + * @param Volume The volume of the sound (0 = off, 1 = full volume) + * @param Pan Panning (-1 = full left, 1 = right) + * @param Loop Indicates whether the sound should be looped + * @param LoopStart Indicates the starting loop point. If a value less than 0 is passed, the start + * of the sound is used. + * @param LoopEnd Indicates the ending loop point. If a avlue is passed less than 0, the end of + * the sound is used. + * @param Layer The sound layer + * @return Returns a handle to the sound. With this handle, the sound can be manipulated during playback. + * @remark If more control is needed over the playing, eg. changing the sound parameters + * for Volume and Panning, then PlaySoundEx should be used. + */ + uint playSoundEx(const Common::String &fileName, SOUND_TYPES type, float volume = 1.0f, float pan = 0.0f, bool loop = false, int loopStart = -1, int loopEnd = -1, uint layer = 0); + + /** + * Sets the volume of a playing sound + * @param Handle The sound handle + * @param Volume The volume of the sound (0 = off, 1 = full volume) + */ + void setSoundVolume(uint handle, float volume); + + /** + * Sets the panning of a playing sound + * @param Handle The sound handle + * @param Pan Panning (-1 = full left, 1 = right) + */ + void setSoundPanning(uint handle, float pan); + + /** + * Pauses a playing sound + * @param Handle The sound handle + */ + void pauseSound(uint handle); + + /** + * Resumes a paused sound + * @param Handle The sound handle + */ + void resumeSound(uint handle); + + /** + * Stops a playing sound + * @param Handle The sound handle + * @remark Calling this method invalidates the passed handle; it can no longer be used. + */ + void stopSound(uint handle); + + /** + * Returns whether a sound is paused + * @param Handle The sound handle + * @return Returns true if the sound is paused, false otherwise. + */ + bool isSoundPaused(uint handle); + + /** + * Returns whether a sound is still playing. + * @param Handle The sound handle + * @return Returns true if the sound is playing, false otherwise. + */ + bool isSoundPlaying(uint handle); + + /** + * Returns the volume of a playing sound (0 = off, 1 = full volume) + */ + float getSoundVolume(uint handle); + + /** + * Returns the panning of a playing sound (-1 = full left, 1 = right) + */ + float getSoundPanning(uint handle); + + /** + * Returns the position within a playing sound in seconds + */ + float getSoundTime(uint handle); + + Resource *loadResource(const Common::String &fileName); + bool canLoadResource(const Common::String &fileName); + + bool persist(OutputPersistenceBlock &writer); + bool unpersist(InputPersistenceBlock &reader); + +private: + bool registerScriptBindings(); + SndHandle *getHandle(uint *id); + +private: + Audio::Mixer *_mixer; + SndHandle _handles[SOUND_HANDLES]; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/sfx/soundengine_script.cpp b/engines/sword25/sfx/soundengine_script.cpp new file mode 100644 index 0000000000..eabbef6e5e --- /dev/null +++ b/engines/sword25/sfx/soundengine_script.cpp @@ -0,0 +1,365 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "sword25/kernel/common.h" +#include "sword25/kernel/kernel.h" +#include "sword25/script/script.h" +#include "sword25/script/luabindhelper.h" + +#include "sword25/sfx/soundengine.h" + +namespace Sword25 { + +static int init(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + if (lua_gettop(L) == 0) + lua_pushbooleancpp(L, pSfx->init(44100, 32)); + else if (lua_gettop(L) == 1) + lua_pushbooleancpp(L, pSfx->init(static_cast<uint>(luaL_checknumber(L, 1)), 32)); + else + lua_pushbooleancpp(L, pSfx->init(static_cast<uint>(luaL_checknumber(L, 1)), static_cast<uint>(luaL_checknumber(L, 2)))); + + return 1; +} + +static int update(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->update(); + + return 0; +} + +static int setVolume(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->setVolume(static_cast<float>(luaL_checknumber(L, 1)), + static_cast<SoundEngine::SOUND_TYPES>(static_cast<uint>(luaL_checknumber(L, 2)))); + + return 0; +} + +static int getVolume(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + lua_pushnumber(L, pSfx->getVolume(static_cast<SoundEngine::SOUND_TYPES>(static_cast<uint>(luaL_checknumber(L, 1))))); + + return 1; +} + +static int pauseAll(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->pauseAll(); + + return 0; +} + +static int resumeAll(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->resumeAll(); + + return 0; +} + +static int pauseLayer(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->pauseLayer(static_cast<int>(luaL_checknumber(L, 1))); + + return 0; +} + +static int resumeLayer(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->resumeLayer(static_cast<int>(luaL_checknumber(L, 1))); + + return 0; +} + +static void processPlayParams(lua_State *L, Common::String &fileName, SoundEngine::SOUND_TYPES &type, float &volume, float &pan, bool &loop, int &loopStart, int &loopEnd, uint &layer) { + fileName = luaL_checkstring(L, 1); + + type = static_cast<SoundEngine::SOUND_TYPES>(static_cast<uint>(luaL_checknumber(L, 2))); + + if (lua_gettop(L) < 3 || lua_isnil(L, 3)) + volume = 1.0f; + else + volume = static_cast<float>(luaL_checknumber(L, 3)); + + if (lua_gettop(L) < 4 || lua_isnil(L, 4)) + pan = 0.0f; + else + pan = static_cast<float>(luaL_checknumber(L, 4)); + + if (lua_gettop(L) < 5 || lua_isnil(L, 5)) + loop = false; + else + loop = lua_tobooleancpp(L, 5); + + if (lua_gettop(L) < 6 || lua_isnil(L, 6)) + loopStart = -1; + else + loopStart = static_cast<int>(luaL_checknumber(L, 6)); + + if (lua_gettop(L) < 7 || lua_isnil(L, 7)) + loopEnd = -1; + else + loopEnd = static_cast<int>(luaL_checknumber(L, 7)); + + if (lua_gettop(L) < 8 || lua_isnil(L, 8)) + layer = 0; + else + layer = static_cast<uint>(luaL_checknumber(L, 8)); +} + +static int playSound(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + Common::String fileName; + SoundEngine::SOUND_TYPES type; + float volume; + float pan; + bool loop; + int loopStart; + int loopEnd; + uint layer; + processPlayParams(L, fileName, type, volume, pan, loop, loopStart, loopEnd, layer); + + lua_pushbooleancpp(L, pSfx->playSound(fileName, type, volume, pan, loop, loopStart, loopEnd, layer)); + + return 1; +} + +static int playSoundEx(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + Common::String fileName; + SoundEngine::SOUND_TYPES type; + float volume; + float pan; + bool loop; + int loopStart; + int loopEnd; + uint layer; + processPlayParams(L, fileName, type, volume, pan, loop, loopStart, loopEnd, layer); + + lua_pushnumber(L, pSfx->playSoundEx(fileName, type, volume, pan, loop, loopStart, loopEnd, layer)); + + return 1; +} + +static int setSoundVolume(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->setSoundVolume(static_cast<uint>(luaL_checknumber(L, 1)), static_cast<float>(luaL_checknumber(L, 2))); + + return 0; +} + +static int setSoundPanning(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->setSoundPanning(static_cast<uint>(luaL_checknumber(L, 1)), static_cast<float>(luaL_checknumber(L, 2))); + + return 0; +} + +static int pauseSound(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->pauseSound(static_cast<uint>(luaL_checknumber(L, 1))); + + return 0; +} + +static int resumeSound(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->resumeSound(static_cast<uint>(luaL_checknumber(L, 1))); + + return 0; +} + +static int stopSound(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + pSfx->stopSound(static_cast<uint>(luaL_checknumber(L, 1))); + + return 0; +} + +static int isSoundPaused(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + lua_pushbooleancpp(L, pSfx->isSoundPaused(static_cast<uint>(luaL_checknumber(L, 1)))); + + return 1; +} + +static int isSoundPlaying(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + lua_pushbooleancpp(L, pSfx->isSoundPlaying(static_cast<uint>(luaL_checknumber(L, 1)))); + + return 1; +} + +static int getSoundVolume(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + lua_pushnumber(L, pSfx->getSoundVolume(static_cast<uint>(luaL_checknumber(L, 1)))); + + return 1; +} + +static int getSoundPanning(lua_State *L) { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + SoundEngine *pSfx = pKernel->getSfx(); + BS_ASSERT(pSfx); + + lua_pushnumber(L, pSfx->getSoundPanning(static_cast<uint>(luaL_checknumber(L, 1)))); + + return 1; +} + +static const char *SFX_LIBRARY_NAME = "Sfx"; + +static const luaL_reg SFX_FUNCTIONS[] = { + {"Init", init}, + {"Update", update}, + {"__SetVolume", setVolume}, + {"__GetVolume", getVolume}, + {"PauseAll", pauseAll}, + {"ResumeAll", resumeAll}, + {"PauseLayer", pauseLayer}, + {"ResumeLayer", resumeLayer}, + {"__PlaySound", playSound}, + {"__PlaySoundEx", playSoundEx}, + {"__SetSoundVolume", setSoundVolume}, + {"__SetSoundPanning", setSoundPanning}, + {"__PauseSound", pauseSound}, + {"__ResumeSound", resumeSound}, + {"__StopSound", stopSound}, + {"__IsSoundPaused", isSoundPaused}, + {"__IsSoundPlaying", isSoundPlaying}, + {"__GetSoundVolume", getSoundVolume}, + {"__GetSoundPanning", getSoundPanning}, + {0, 0} +}; + +static const lua_constant_reg SFX_CONSTANTS[] = { + {"MUSIC", SoundEngine::MUSIC}, + {"SPEECH", SoundEngine::SPEECH}, + {"SFX", SoundEngine::SFX}, + {0, 0} +}; + +bool SoundEngine::registerScriptBindings() { + Kernel *pKernel = Kernel::getInstance(); + BS_ASSERT(pKernel); + ScriptEngine *pScript = pKernel->getScript(); + BS_ASSERT(pScript); + lua_State *L = static_cast<lua_State *>(pScript->getScriptObject()); + BS_ASSERT(L); + + if (!LuaBindhelper::addFunctionsToLib(L, SFX_LIBRARY_NAME, SFX_FUNCTIONS)) return false; + if (!LuaBindhelper::addConstantsToLib(L, SFX_LIBRARY_NAME, SFX_CONSTANTS)) return false; + + return true; +} + +} // End of namespace Sword25 diff --git a/engines/sword25/sword25.cpp b/engines/sword25/sword25.cpp new file mode 100644 index 0000000000..5864057423 --- /dev/null +++ b/engines/sword25/sword25.cpp @@ -0,0 +1,202 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +/* + * This code is based on Broken Sword 2.5 engine + * + * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer + * + * Licensed under GNU GPL v2 + * + */ + +#include "common/config-manager.h" +#include "common/debug-channels.h" +#include "engines/util.h" + +#include "sword25/sword25.h" +#include "sword25/kernel/filesystemutil.h" +#include "sword25/kernel/kernel.h" +#include "sword25/kernel/persistenceservice.h" +#include "sword25/package/packagemanager.h" +#include "sword25/script/script.h" + +#include "sword25/gfx/animationtemplateregistry.h" // Needed so we can destroy the singleton +#include "sword25/gfx/renderobjectregistry.h" // Needed so we can destroy the singleton +#include "sword25/math/regionregistry.h" // Needed so we can destroy the singleton + +namespace Sword25 { + +#define BS_LOG_PREFIX "MAIN" + +const char *const PACKAGE_MANAGER = "archiveFS"; +const char *const DEFAULT_SCRIPT_FILE = "/system/boot.lua"; + +Sword25Engine::Sword25Engine(OSystem *syst, const ADGameDescription *gameDesc): + Engine(syst), + _gameDescription(gameDesc) { + + DebugMan.addDebugChannel(kDebugScript, "Script", "Script debug level"); + DebugMan.addDebugChannel(kDebugScript, "Scripts", "Script debug level"); + DebugMan.addDebugChannel(kDebugSound, "Sound", "Sound debug level"); +} + +Sword25Engine::~Sword25Engine() { +} + +Common::Error Sword25Engine::run() { + // Engine initialisation + Common::Error errorCode = appStart(); + if (errorCode != Common::kNoError) { + appEnd(); + return errorCode; + } + + // Run the game + bool runSuccess = appMain(); + + // Engine de-initialisation + bool deinitSuccess = appEnd(); + + return (runSuccess && deinitSuccess) ? Common::kNoError : Common::kUnknownError; +} + +Common::Error Sword25Engine::appStart() { + // Initialise the graphics mode to ARGB8888 + Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24); + initGraphics(800, 600, true, &format); + if (format != g_system->getScreenFormat()) + return Common::kUnsupportedColorMode; + + // Kernel initialization + if (!Kernel::getInstance()->getInitSuccess()) { + BS_LOG_ERRORLN("Kernel initialization failed."); + return Common::kUnknownError; + } + + // Load packages + PackageManager *packageManagerPtr = Kernel::getInstance()->getPackage(); + if (getGameFlags() & GF_EXTRACTED) { + if (!packageManagerPtr->loadDirectoryAsPackage(ConfMan.get("path"), "/")) + return Common::kUnknownError; + } else { + if (!loadPackages()) + return Common::kUnknownError; + } + + // Pass the command line to the script engine. + ScriptEngine *scriptPtr = Kernel::getInstance()->getScript(); + if (!scriptPtr) { + BS_LOG_ERRORLN("Script intialization failed."); + return Common::kUnknownError; + } + + // Set the game target for use in savegames + setGameTarget(_targetName.c_str()); + + Common::StringArray commandParameters; + scriptPtr->setCommandLine(commandParameters); + + return Common::kNoError; +} + +bool Sword25Engine::appMain() { + // The main script start. This script loads all the other scripts and starts the actual game. + ScriptEngine *scriptPtr = Kernel::getInstance()->getScript(); + BS_ASSERT(scriptPtr); + scriptPtr->executeFile(DEFAULT_SCRIPT_FILE); + + return true; +} + +bool Sword25Engine::appEnd() { + // The kernel is shutdown, and un-initialises all subsystems + Kernel::deleteInstance(); + + AnimationTemplateRegistry::destroy(); + RenderObjectRegistry::destroy(); + RegionRegistry::destroy(); + + // Free the log file if it was used + BS_Log::closeLog(); + + return true; +} + +bool Sword25Engine::loadPackages() { + PackageManager *packageManagerPtr = Kernel::getInstance()->getPackage(); + BS_ASSERT(packageManagerPtr); + + // Load the main package + if (!packageManagerPtr->loadPackage("data.b25c", "/")) + return false; + + // Get the contents of the main program directory and sort them alphabetically + Common::FSNode dir(ConfMan.get("path")); + Common::FSList files; + if (!dir.isDirectory() || !dir.getChildren(files, Common::FSNode::kListAll)) { + warning("Game data path does not exist or is not a directory"); + return false; + } + + Common::sort(files.begin(), files.end()); + + // Identify all patch packages + // The filename of patch packages must have the form patch??.b25c, with the question marks + // are placeholders for numbers. + // Since the filenames have been sorted, patches are mounted with low numbers first, through + // to ones with high numbers. This is important, because newly mount packages overwrite + // existing files in the virtual file system, if they include files with the same name. + for (Common::FSList::const_iterator it = files.begin(); it != files.end(); ++it) { + if (it->getName().matchString("patch???.b25c", true)) + if (!packageManagerPtr->loadPackage(it->getName(), "/")) + return false; + } + + // Identify and mount all language packages + // The filename of the packages have the form lang_*.b25c (eg. lang_de.b25c) + for (Common::FSList::const_iterator it = files.begin(); it != files.end(); ++it) { + if (it->getName().matchString("lang_*.b25c", true)) + if (!packageManagerPtr->loadPackage(it->getName(), "/")) + return false; + } + + return true; +} + +bool Sword25Engine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL); + // TODO: Implement more of these features?! +#if 0 + return + (f == kSupportsSubtitleOptions) || + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +#endif +} + +} // End of namespace Sword25 diff --git a/engines/sword25/sword25.h b/engines/sword25/sword25.h new file mode 100644 index 0000000000..2d93e2267c --- /dev/null +++ b/engines/sword25/sword25.h @@ -0,0 +1,96 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SWORD25_H +#define SWORD25_H + +#include "common/scummsys.h" +#include "common/str-array.h" +#include "common/util.h" +#include "engines/engine.h" + +#include "sword25/kernel/log.h" + +struct ADGameDescription; + +/** + * This is the namespace of the Sword25 engine. + * + * Status of this engine: ??? + * + * Games using this engine: + * - Broken Sword 2.5 + */ +namespace Sword25 { + +enum { + kFileTypeHash = 0 +}; + +enum { + kDebugScript = 1 << 0, + kDebugSound = 1 << 1 +}; + +enum GameFlags { + GF_EXTRACTED = 1 << 0 +}; + +#define MESSAGE_BASIC 1 +#define MESSAGE_INTERMEDIATE 2 +#define MESSAGE_DETAILED 3 + +class Sword25Engine : public Engine { +private: + Common::Error appStart(); + bool appMain(); + bool appEnd(); + + bool loadPackages(); + +protected: + virtual Common::Error run(); + bool hasFeature(EngineFeature f) const; +// void pauseEngineIntern(bool pause); // TODO: Implement this!!! +// void syncSoundSettings(); // TODO: Implement this!!! +// Common::Error loadGameState(int slot); // TODO: Implement this? +// Common::Error saveGameState(int slot, const char *desc); // TODO: Implement this? +// bool canLoadGameStateCurrently(); // TODO: Implement this? +// bool canSaveGameStateCurrently(); // TODO: Implement this? + + void shutdown(); + +public: + Sword25Engine(OSystem *syst, const ADGameDescription *gameDesc); + virtual ~Sword25Engine(); + + uint32 getGameFlags() const; + + const ADGameDescription *_gameDescription; +}; + +} // End of namespace Sword25 + +#endif diff --git a/engines/sword25/util/lua/COPYRIGHT b/engines/sword25/util/lua/COPYRIGHT new file mode 100644 index 0000000000..3a53e741e0 --- /dev/null +++ b/engines/sword25/util/lua/COPYRIGHT @@ -0,0 +1,34 @@ +Lua License +----------- + +Lua is licensed under the terms of the MIT license reproduced below. +This means that Lua is free software and can be used for both academic +and commercial purposes at absolutely no cost. + +For details and rationale, see http://www.lua.org/license.html . + +=============================================================================== + +Copyright (C) 1994-2008 Lua.org, PUC-Rio. + +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. + +=============================================================================== + +(end of COPYRIGHT) diff --git a/engines/sword25/util/lua/HISTORY b/engines/sword25/util/lua/HISTORY new file mode 100644 index 0000000000..ce0c95bc69 --- /dev/null +++ b/engines/sword25/util/lua/HISTORY @@ -0,0 +1,183 @@ +HISTORY for Lua 5.1 + +* Changes from version 5.0 to 5.1 + ------------------------------- + Language: + + new module system. + + new semantics for control variables of fors. + + new semantics for setn/getn. + + new syntax/semantics for varargs. + + new long strings and comments. + + new `mod' operator (`%') + + new length operator #t + + metatables for all types + API: + + new functions: lua_createtable, lua_get(set)field, lua_push(to)integer. + + user supplies memory allocator (lua_open becomes lua_newstate). + + luaopen_* functions must be called through Lua. + Implementation: + + new configuration scheme via luaconf.h. + + incremental garbage collection. + + better handling of end-of-line in the lexer. + + fully reentrant parser (new Lua function `load') + + better support for 64-bit machines. + + native loadlib support for Mac OS X. + + standard distribution in only one library (lualib.a merged into lua.a) + +* Changes from version 4.0 to 5.0 + ------------------------------- + Language: + + lexical scoping. + + Lua coroutines. + + standard libraries now packaged in tables. + + tags replaced by metatables and tag methods replaced by metamethods, + stored in metatables. + + proper tail calls. + + each function can have its own global table, which can be shared. + + new __newindex metamethod, called when we insert a new key into a table. + + new block comments: --[[ ... ]]. + + new generic for. + + new weak tables. + + new boolean type. + + new syntax "local function". + + (f()) returns the first value returned by f. + + {f()} fills a table with all values returned by f. + + \n ignored in [[\n . + + fixed and-or priorities. + + more general syntax for function definition (e.g. function a.x.y:f()...end). + + more general syntax for function calls (e.g. (print or write)(9)). + + new functions (time/date, tmpfile, unpack, require, load*, etc.). + API: + + chunks are loaded by using lua_load; new luaL_loadfile and luaL_loadbuffer. + + introduced lightweight userdata, a simple "void*" without a metatable. + + new error handling protocol: the core no longer prints error messages; + all errors are reported to the caller on the stack. + + new lua_atpanic for host cleanup. + + new, signal-safe, hook scheme. + Implementation: + + new license: MIT. + + new, faster, register-based virtual machine. + + support for external multithreading and coroutines. + + new and consistent error message format. + + the core no longer needs "stdio.h" for anything (except for a single + use of sprintf to convert numbers to strings). + + lua.c now runs the environment variable LUA_INIT, if present. It can + be "@filename", to run a file, or the chunk itself. + + support for user extensions in lua.c. + sample implementation given for command line editing. + + new dynamic loading library, active by default on several platforms. + + safe garbage-collector metamethods. + + precompiled bytecodes checked for integrity (secure binary dostring). + + strings are fully aligned. + + position capture in string.find. + + read('*l') can read lines with embedded zeros. + +* Changes from version 3.2 to 4.0 + ------------------------------- + Language: + + new "break" and "for" statements (both numerical and for tables). + + uniform treatment of globals: globals are now stored in a Lua table. + + improved error messages. + + no more '$debug': full speed *and* full debug information. + + new read form: read(N) for next N bytes. + + general read patterns now deprecated. + (still available with -DCOMPAT_READPATTERNS.) + + all return values are passed as arguments for the last function + (old semantics still available with -DLUA_COMPAT_ARGRET) + + garbage collection tag methods for tables now deprecated. + + there is now only one tag method for order. + API: + + New API: fully re-entrant, simpler, and more efficient. + + New debug API. + Implementation: + + faster than ever: cleaner virtual machine and new hashing algorithm. + + non-recursive garbage-collector algorithm. + + reduced memory usage for programs with many strings. + + improved treatment for memory allocation errors. + + improved support for 16-bit machines (we hope). + + code now compiles unmodified as both ANSI C and C++. + + numbers in bases other than 10 are converted using strtoul. + + new -f option in Lua to support #! scripts. + + luac can now combine text and binaries. + +* Changes from version 3.1 to 3.2 + ------------------------------- + + redirected all output in Lua's core to _ERRORMESSAGE and _ALERT. + + increased limit on the number of constants and globals per function + (from 2^16 to 2^24). + + debugging info (lua_debug and hooks) moved into lua_state and new API + functions provided to get and set this info. + + new debug lib gives full debugging access within Lua. + + new table functions "foreachi", "sort", "tinsert", "tremove", "getn". + + new io functions "flush", "seek". + +* Changes from version 3.0 to 3.1 + ------------------------------- + + NEW FEATURE: anonymous functions with closures (via "upvalues"). + + new syntax: + - local variables in chunks. + - better scope control with DO block END. + - constructors can now be also written: { record-part; list-part }. + - more general syntax for function calls and lvalues, e.g.: + f(x).y=1 + o:f(x,y):g(z) + f"string" is sugar for f("string") + + strings may now contain arbitrary binary data (e.g., embedded zeros). + + major code re-organization and clean-up; reduced module interdependecies. + + no arbitrary limits on the total number of constants and globals. + + support for multiple global contexts. + + better syntax error messages. + + new traversal functions "foreach" and "foreachvar". + + the default for numbers is now double. + changing it to use floats or longs is easy. + + complete debug information stored in pre-compiled chunks. + + sample interpreter now prompts user when run interactively, and also + handles control-C interruptions gracefully. + +* Changes from version 2.5 to 3.0 + ------------------------------- + + NEW CONCEPT: "tag methods". + Tag methods replace fallbacks as the meta-mechanism for extending the + semantics of Lua. Whereas fallbacks had a global nature, tag methods + work on objects having the same tag (e.g., groups of tables). + Existing code that uses fallbacks should work without change. + + new, general syntax for constructors {[exp] = exp, ... }. + + support for handling variable number of arguments in functions (varargs). + + support for conditional compilation ($if ... $else ... $end). + + cleaner semantics in API simplifies host code. + + better support for writing libraries (auxlib.h). + + better type checking and error messages in the standard library. + + luac can now also undump. + +* Changes from version 2.4 to 2.5 + ------------------------------- + + io and string libraries are now based on pattern matching; + the old libraries are still available for compatibility + + dofile and dostring can now return values (via return statement) + + better support for 16- and 64-bit machines + + expanded documentation, with more examples + +* Changes from version 2.2 to 2.4 + ------------------------------- + + external compiler creates portable binary files that can be loaded faster + + interface for debugging and profiling + + new "getglobal" fallback + + new functions for handling references to Lua objects + + new functions in standard lib + + only one copy of each string is stored + + expanded documentation, with more examples + +* Changes from version 2.1 to 2.2 + ------------------------------- + + functions now may be declared with any "lvalue" as a name + + garbage collection of functions + + support for pipes + +* Changes from version 1.1 to 2.1 + ------------------------------- + + object-oriented support + + fallbacks + + simplified syntax for tables + + many internal improvements + +(end of HISTORY) diff --git a/engines/sword25/util/lua/README b/engines/sword25/util/lua/README new file mode 100644 index 0000000000..11b4dff70e --- /dev/null +++ b/engines/sword25/util/lua/README @@ -0,0 +1,37 @@ +README for Lua 5.1 + +See INSTALL for installation instructions. +See HISTORY for a summary of changes since the last released version. + +* What is Lua? + ------------ + Lua is a powerful, light-weight programming language designed for extending + applications. Lua is also frequently used as a general-purpose, stand-alone + language. Lua is free software. + + For complete information, visit Lua's web site at http://www.lua.org/ . + For an executive summary, see http://www.lua.org/about.html . + + Lua has been used in many different projects around the world. + For a short list, see http://www.lua.org/uses.html . + +* Availability + ------------ + Lua is freely available for both academic and commercial purposes. + See COPYRIGHT and http://www.lua.org/license.html for details. + Lua can be downloaded at http://www.lua.org/download.html . + +* Installation + ------------ + Lua is implemented in pure ANSI C, and compiles unmodified in all known + platforms that have an ANSI C compiler. In most Unix-like platforms, simply + do "make" with a suitable target. See INSTALL for detailed instructions. + +* Origin + ------ + Lua is developed at Lua.org, a laboratory of the Department of Computer + Science of PUC-Rio (the Pontifical Catholic University of Rio de Janeiro + in Brazil). + For more information about the authors, see http://www.lua.org/authors.html . + +(end of README) diff --git a/engines/sword25/util/lua/lapi.cpp b/engines/sword25/util/lua/lapi.cpp new file mode 100644 index 0000000000..a38733e1f7 --- /dev/null +++ b/engines/sword25/util/lua/lapi.cpp @@ -0,0 +1,1087 @@ +/* +** $Id$ +** Lua API +** See Copyright Notice in lua.h +*/ + + +#include <assert.h> +#include <math.h> +#include <stdarg.h> +#include <string.h> + +#define lapi_c +#define LUA_CORE + +#include "lua.h" + +#include "lapi.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lundump.h" +#include "lvm.h" + + + +const char lua_ident[] = + "$Lua: " LUA_RELEASE " " LUA_COPYRIGHT " $\n" + "$Authors: " LUA_AUTHORS " $\n" + "$URL: www.lua.org $\n"; + + + +#define api_checknelems(L, n) api_check(L, (n) <= (L->top - L->base)) + +#define api_checkvalidindex(L, i) api_check(L, (i) != luaO_nilobject) + +#define api_incr_top(L) {api_check(L, L->top < L->ci->top); L->top++;} + + + +static TValue *index2adr (lua_State *L, int idx) { + if (idx > 0) { + TValue *o = L->base + (idx - 1); + api_check(L, idx <= L->ci->top - L->base); + // FIXME: Get rid of const_cast + if (o >= L->top) return const_cast<TValue *>(luaO_nilobject); + else return o; + } + else if (idx > LUA_REGISTRYINDEX) { + api_check(L, idx != 0 && -idx <= L->top - L->base); + return L->top + idx; + } + else switch (idx) { /* pseudo-indices */ + case LUA_REGISTRYINDEX: return registry(L); + case LUA_ENVIRONINDEX: { + Closure *func = curr_func(L); + sethvalue(L, &L->env, func->c.env); + return &L->env; + } + case LUA_GLOBALSINDEX: return gt(L); + default: { + Closure *func = curr_func(L); + idx = LUA_GLOBALSINDEX - idx; + return (idx <= func->c.nupvalues) + ? &func->c.upvalue[idx-1] + // FIXME: Get rid of const_cast + : const_cast<TValue *>(luaO_nilobject); + } + } +} + + +static Table *getcurrenv (lua_State *L) { + if (L->ci == L->base_ci) /* no enclosing function? */ + return hvalue(gt(L)); /* use global table as environment */ + else { + Closure *func = curr_func(L); + return func->c.env; + } +} + + +void luaA_pushobject (lua_State *L, const TValue *o) { + setobj2s(L, L->top, o); + api_incr_top(L); +} + + +LUA_API int lua_checkstack (lua_State *L, int size) { + int res; + lua_lock(L); + if ((L->top - L->base + size) > LUAI_MAXCSTACK) + res = 0; /* stack overflow */ + else { + luaD_checkstack(L, size); + if (L->ci->top < L->top + size) + L->ci->top = L->top + size; + res = 1; + } + lua_unlock(L); + return res; +} + + +LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { + int i; + if (from == to) return; + lua_lock(to); + api_checknelems(from, n); + api_check(from, G(from) == G(to)); + api_check(from, to->ci->top - to->top >= n); + from->top -= n; + for (i = 0; i < n; i++) { + setobj2s(to, to->top++, from->top + i); + } + lua_unlock(to); +} + + +LUA_API void lua_setlevel (lua_State *from, lua_State *to) { + to->nCcalls = from->nCcalls; +} + + +LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) { + lua_CFunction old; + lua_lock(L); + old = G(L)->panic; + G(L)->panic = panicf; + lua_unlock(L); + return old; +} + + +LUA_API lua_State *lua_newthread (lua_State *L) { + lua_State *L1; + lua_lock(L); + luaC_checkGC(L); + L1 = luaE_newthread(L); + setthvalue(L, L->top, L1); + api_incr_top(L); + lua_unlock(L); + luai_userstatethread(L, L1); + return L1; +} + + + +/* +** basic stack manipulation +*/ + + +LUA_API int lua_gettop (lua_State *L) { + return cast_int(L->top - L->base); +} + + +LUA_API void lua_settop (lua_State *L, int idx) { + lua_lock(L); + if (idx >= 0) { + api_check(L, idx <= L->stack_last - L->base); + while (L->top < L->base + idx) + setnilvalue(L->top++); + L->top = L->base + idx; + } + else { + api_check(L, -(idx+1) <= (L->top - L->base)); + L->top += idx+1; /* `subtract' index (index is negative) */ + } + lua_unlock(L); +} + + +LUA_API void lua_remove (lua_State *L, int idx) { + StkId p; + lua_lock(L); + p = index2adr(L, idx); + api_checkvalidindex(L, p); + while (++p < L->top) setobjs2s(L, p-1, p); + L->top--; + lua_unlock(L); +} + + +LUA_API void lua_insert (lua_State *L, int idx) { + StkId p; + StkId q; + lua_lock(L); + p = index2adr(L, idx); + api_checkvalidindex(L, p); + for (q = L->top; q>p; q--) setobjs2s(L, q, q-1); + setobjs2s(L, p, L->top); + lua_unlock(L); +} + + +LUA_API void lua_replace (lua_State *L, int idx) { + StkId o; + lua_lock(L); + /* explicit test for incompatible code */ + if (idx == LUA_ENVIRONINDEX && L->ci == L->base_ci) + luaG_runerror(L, "no calling environment"); + api_checknelems(L, 1); + o = index2adr(L, idx); + api_checkvalidindex(L, o); + if (idx == LUA_ENVIRONINDEX) { + Closure *func = curr_func(L); + api_check(L, ttistable(L->top - 1)); + func->c.env = hvalue(L->top - 1); + luaC_barrier(L, func, L->top - 1); + } + else { + setobj(L, o, L->top - 1); + if (idx < LUA_GLOBALSINDEX) /* function upvalue? */ + luaC_barrier(L, curr_func(L), L->top - 1); + } + L->top--; + lua_unlock(L); +} + + +LUA_API void lua_pushvalue (lua_State *L, int idx) { + lua_lock(L); + setobj2s(L, L->top, index2adr(L, idx)); + api_incr_top(L); + lua_unlock(L); +} + + + +/* +** access functions (stack -> C) +*/ + + +LUA_API int lua_type (lua_State *L, int idx) { + StkId o = index2adr(L, idx); + return (o == luaO_nilobject) ? LUA_TNONE : ttype(o); +} + + +LUA_API const char *lua_typename (lua_State *L, int t) { + UNUSED(L); + return (t == LUA_TNONE) ? "no value" : luaT_typenames[t]; +} + + +LUA_API int lua_iscfunction (lua_State *L, int idx) { + StkId o = index2adr(L, idx); + return iscfunction(o); +} + + +LUA_API int lua_isnumber (lua_State *L, int idx) { + TValue n; + const TValue *o = index2adr(L, idx); + return tonumber(o, &n); +} + + +LUA_API int lua_isstring (lua_State *L, int idx) { + int t = lua_type(L, idx); + return (t == LUA_TSTRING || t == LUA_TNUMBER); +} + + +LUA_API int lua_isuserdata (lua_State *L, int idx) { + const TValue *o = index2adr(L, idx); + return (ttisuserdata(o) || ttislightuserdata(o)); +} + + +LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { + StkId o1 = index2adr(L, index1); + StkId o2 = index2adr(L, index2); + return (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 + : luaO_rawequalObj(o1, o2); +} + + +LUA_API int lua_equal (lua_State *L, int index1, int index2) { + StkId o1, o2; + int i; + lua_lock(L); /* may call tag method */ + o1 = index2adr(L, index1); + o2 = index2adr(L, index2); + i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : equalobj(L, o1, o2); + lua_unlock(L); + return i; +} + + +LUA_API int lua_lessthan (lua_State *L, int index1, int index2) { + StkId o1, o2; + int i; + lua_lock(L); /* may call tag method */ + o1 = index2adr(L, index1); + o2 = index2adr(L, index2); + i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 + : luaV_lessthan(L, o1, o2); + lua_unlock(L); + return i; +} + + + +LUA_API lua_Number lua_tonumber (lua_State *L, int idx) { + TValue n; + const TValue *o = index2adr(L, idx); + if (tonumber(o, &n)) + return nvalue(o); + else + return 0; +} + + +LUA_API lua_Integer lua_tointeger (lua_State *L, int idx) { + TValue n; + const TValue *o = index2adr(L, idx); + if (tonumber(o, &n)) { + lua_Integer res; + lua_Number num = nvalue(o); + lua_number2integer(res, num); + return res; + } + else + return 0; +} + + +LUA_API int lua_toboolean (lua_State *L, int idx) { + const TValue *o = index2adr(L, idx); + return !l_isfalse(o); +} + + +LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { + StkId o = index2adr(L, idx); + if (!ttisstring(o)) { + lua_lock(L); /* `luaV_tostring' may create a new string */ + if (!luaV_tostring(L, o)) { /* conversion failed? */ + if (len != NULL) *len = 0; + lua_unlock(L); + return NULL; + } + luaC_checkGC(L); + o = index2adr(L, idx); /* previous call may reallocate the stack */ + lua_unlock(L); + } + if (len != NULL) *len = tsvalue(o)->len; + return svalue(o); +} + + +LUA_API size_t lua_objlen (lua_State *L, int idx) { + StkId o = index2adr(L, idx); + switch (ttype(o)) { + case LUA_TSTRING: return tsvalue(o)->len; + case LUA_TUSERDATA: return uvalue(o)->len; + case LUA_TTABLE: return luaH_getn(hvalue(o)); + case LUA_TNUMBER: { + size_t l; + lua_lock(L); /* `luaV_tostring' may create a new string */ + l = (luaV_tostring(L, o) ? tsvalue(o)->len : 0); + lua_unlock(L); + return l; + } + default: return 0; + } +} + + +LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { + StkId o = index2adr(L, idx); + return (!iscfunction(o)) ? NULL : clvalue(o)->c.f; +} + + +LUA_API void *lua_touserdata (lua_State *L, int idx) { + StkId o = index2adr(L, idx); + switch (ttype(o)) { + case LUA_TUSERDATA: return (rawuvalue(o) + 1); + case LUA_TLIGHTUSERDATA: return pvalue(o); + default: return NULL; + } +} + + +LUA_API lua_State *lua_tothread (lua_State *L, int idx) { + StkId o = index2adr(L, idx); + return (!ttisthread(o)) ? NULL : thvalue(o); +} + + +LUA_API const void *lua_topointer (lua_State *L, int idx) { + StkId o = index2adr(L, idx); + switch (ttype(o)) { + case LUA_TTABLE: return hvalue(o); + case LUA_TFUNCTION: return clvalue(o); + case LUA_TTHREAD: return thvalue(o); + case LUA_TUSERDATA: + case LUA_TLIGHTUSERDATA: + return lua_touserdata(L, idx); + default: return NULL; + } +} + + + +/* +** push functions (C -> stack) +*/ + + +LUA_API void lua_pushnil (lua_State *L) { + lua_lock(L); + setnilvalue(L->top); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { + lua_lock(L); + setnvalue(L->top, n); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { + lua_lock(L); + setnvalue(L->top, cast_num(n)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushlstring (lua_State *L, const char *s, size_t len) { + lua_lock(L); + luaC_checkGC(L); + setsvalue2s(L, L->top, luaS_newlstr(L, s, len)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushstring (lua_State *L, const char *s) { + if (s == NULL) + lua_pushnil(L); + else + lua_pushlstring(L, s, strlen(s)); +} + + +LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, + va_list argp) { + const char *ret; + lua_lock(L); + luaC_checkGC(L); + ret = luaO_pushvfstring(L, fmt, argp); + lua_unlock(L); + return ret; +} + + +LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { + const char *ret; + va_list argp; + lua_lock(L); + luaC_checkGC(L); + va_start(argp, fmt); + ret = luaO_pushvfstring(L, fmt, argp); + va_end(argp); + lua_unlock(L); + return ret; +} + + +LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { + Closure *cl; + lua_lock(L); + luaC_checkGC(L); + api_checknelems(L, n); + cl = luaF_newCclosure(L, n, getcurrenv(L)); + cl->c.f = fn; + L->top -= n; + while (n--) + setobj2n(L, &cl->c.upvalue[n], L->top+n); + setclvalue(L, L->top, cl); + lua_assert(iswhite(obj2gco(cl))); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushboolean (lua_State *L, int b) { + lua_lock(L); + setbvalue(L->top, (b != 0)); /* ensure that true is 1 */ + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_pushlightuserdata (lua_State *L, void *p) { + lua_lock(L); + setpvalue(L->top, p); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API int lua_pushthread (lua_State *L) { + lua_lock(L); + setthvalue(L, L->top, L); + api_incr_top(L); + lua_unlock(L); + return (G(L)->mainthread == L); +} + + + +/* +** get functions (Lua -> stack) +*/ + + +LUA_API void lua_gettable (lua_State *L, int idx) { + StkId t; + lua_lock(L); + t = index2adr(L, idx); + api_checkvalidindex(L, t); + luaV_gettable(L, t, L->top - 1, L->top - 1); + lua_unlock(L); +} + + +LUA_API void lua_getfield (lua_State *L, int idx, const char *k) { + StkId t; + TValue key; + lua_lock(L); + t = index2adr(L, idx); + api_checkvalidindex(L, t); + setsvalue(L, &key, luaS_new(L, k)); + luaV_gettable(L, t, &key, L->top); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_rawget (lua_State *L, int idx) { + StkId t; + lua_lock(L); + t = index2adr(L, idx); + api_check(L, ttistable(t)); + setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1)); + lua_unlock(L); +} + + +LUA_API void lua_rawgeti (lua_State *L, int idx, int n) { + StkId o; + lua_lock(L); + o = index2adr(L, idx); + api_check(L, ttistable(o)); + setobj2s(L, L->top, luaH_getnum(hvalue(o), n)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { + lua_lock(L); + luaC_checkGC(L); + sethvalue(L, L->top, luaH_new(L, narray, nrec)); + api_incr_top(L); + lua_unlock(L); +} + + +LUA_API int lua_getmetatable (lua_State *L, int objindex) { + const TValue *obj; + Table *mt = NULL; + int res; + lua_lock(L); + obj = index2adr(L, objindex); + switch (ttype(obj)) { + case LUA_TTABLE: + mt = hvalue(obj)->metatable; + break; + case LUA_TUSERDATA: + mt = uvalue(obj)->metatable; + break; + default: + mt = G(L)->mt[ttype(obj)]; + break; + } + if (mt == NULL) + res = 0; + else { + sethvalue(L, L->top, mt); + api_incr_top(L); + res = 1; + } + lua_unlock(L); + return res; +} + + +LUA_API void lua_getfenv (lua_State *L, int idx) { + StkId o; + lua_lock(L); + o = index2adr(L, idx); + api_checkvalidindex(L, o); + switch (ttype(o)) { + case LUA_TFUNCTION: + sethvalue(L, L->top, clvalue(o)->c.env); + break; + case LUA_TUSERDATA: + sethvalue(L, L->top, uvalue(o)->env); + break; + case LUA_TTHREAD: + setobj2s(L, L->top, gt(thvalue(o))); + break; + default: + setnilvalue(L->top); + break; + } + api_incr_top(L); + lua_unlock(L); +} + + +/* +** set functions (stack -> Lua) +*/ + + +LUA_API void lua_settable (lua_State *L, int idx) { + StkId t; + lua_lock(L); + api_checknelems(L, 2); + t = index2adr(L, idx); + api_checkvalidindex(L, t); + luaV_settable(L, t, L->top - 2, L->top - 1); + L->top -= 2; /* pop index and value */ + lua_unlock(L); +} + + +LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { + StkId t; + TValue key; + lua_lock(L); + api_checknelems(L, 1); + t = index2adr(L, idx); + api_checkvalidindex(L, t); + setsvalue(L, &key, luaS_new(L, k)); + luaV_settable(L, t, &key, L->top - 1); + L->top--; /* pop value */ + lua_unlock(L); +} + + +LUA_API void lua_rawset (lua_State *L, int idx) { + StkId t; + lua_lock(L); + api_checknelems(L, 2); + t = index2adr(L, idx); + api_check(L, ttistable(t)); + setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1); + luaC_barriert(L, hvalue(t), L->top-1); + L->top -= 2; + lua_unlock(L); +} + + +LUA_API void lua_rawseti (lua_State *L, int idx, int n) { + StkId o; + lua_lock(L); + api_checknelems(L, 1); + o = index2adr(L, idx); + api_check(L, ttistable(o)); + setobj2t(L, luaH_setnum(L, hvalue(o), n), L->top-1); + luaC_barriert(L, hvalue(o), L->top-1); + L->top--; + lua_unlock(L); +} + + +LUA_API int lua_setmetatable (lua_State *L, int objindex) { + TValue *obj; + Table *mt; + lua_lock(L); + api_checknelems(L, 1); + obj = index2adr(L, objindex); + api_checkvalidindex(L, obj); + if (ttisnil(L->top - 1)) + mt = NULL; + else { + api_check(L, ttistable(L->top - 1)); + mt = hvalue(L->top - 1); + } + switch (ttype(obj)) { + case LUA_TTABLE: { + hvalue(obj)->metatable = mt; + if (mt) + luaC_objbarriert(L, hvalue(obj), mt); + break; + } + case LUA_TUSERDATA: { + uvalue(obj)->metatable = mt; + if (mt) + luaC_objbarrier(L, rawuvalue(obj), mt); + break; + } + default: { + G(L)->mt[ttype(obj)] = mt; + break; + } + } + L->top--; + lua_unlock(L); + return 1; +} + + +LUA_API int lua_setfenv (lua_State *L, int idx) { + StkId o; + int res = 1; + lua_lock(L); + api_checknelems(L, 1); + o = index2adr(L, idx); + api_checkvalidindex(L, o); + api_check(L, ttistable(L->top - 1)); + switch (ttype(o)) { + case LUA_TFUNCTION: + clvalue(o)->c.env = hvalue(L->top - 1); + break; + case LUA_TUSERDATA: + uvalue(o)->env = hvalue(L->top - 1); + break; + case LUA_TTHREAD: + sethvalue(L, gt(thvalue(o)), hvalue(L->top - 1)); + break; + default: + res = 0; + break; + } + if (res) luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1)); + L->top--; + lua_unlock(L); + return res; +} + + +/* +** `load' and `call' functions (run Lua code) +*/ + + +#define adjustresults(L,nres) \ + { if (nres == LUA_MULTRET && L->top >= L->ci->top) L->ci->top = L->top; } + + +#define checkresults(L,na,nr) \ + api_check(L, (nr) == LUA_MULTRET || (L->ci->top - L->top >= (nr) - (na))) + + +LUA_API void lua_call (lua_State *L, int nargs, int nresults) { + StkId func; + lua_lock(L); + api_checknelems(L, nargs+1); + checkresults(L, nargs, nresults); + func = L->top - (nargs+1); + luaD_call(L, func, nresults); + adjustresults(L, nresults); + lua_unlock(L); +} + + + +/* +** Execute a protected call. +*/ +struct CallS { /* data to `f_call' */ + StkId func; + int nresults; +}; + + +static void f_call (lua_State *L, void *ud) { + struct CallS *c = cast(struct CallS *, ud); + luaD_call(L, c->func, c->nresults); +} + + + +LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) { + struct CallS c; + int status; + ptrdiff_t func; + lua_lock(L); + api_checknelems(L, nargs+1); + checkresults(L, nargs, nresults); + if (errfunc == 0) + func = 0; + else { + StkId o = index2adr(L, errfunc); + api_checkvalidindex(L, o); + func = savestack(L, o); + } + c.func = L->top - (nargs+1); /* function to be called */ + c.nresults = nresults; + status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); + adjustresults(L, nresults); + lua_unlock(L); + return status; +} + + +/* +** Execute a protected C call. +*/ +struct CCallS { /* data to `f_Ccall' */ + lua_CFunction func; + void *ud; +}; + + +static void f_Ccall (lua_State *L, void *ud) { + struct CCallS *c = cast(struct CCallS *, ud); + Closure *cl; + cl = luaF_newCclosure(L, 0, getcurrenv(L)); + cl->c.f = c->func; + setclvalue(L, L->top, cl); /* push function */ + api_incr_top(L); + setpvalue(L->top, c->ud); /* push only argument */ + api_incr_top(L); + luaD_call(L, L->top - 2, 0); +} + + +LUA_API int lua_cpcall (lua_State *L, lua_CFunction func, void *ud) { + struct CCallS c; + int status; + lua_lock(L); + c.func = func; + c.ud = ud; + status = luaD_pcall(L, f_Ccall, &c, savestack(L, L->top), 0); + lua_unlock(L); + return status; +} + + +LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, + const char *chunkname) { + ZIO z; + int status; + lua_lock(L); + if (!chunkname) chunkname = "?"; + luaZ_init(L, &z, reader, data); + status = luaD_protectedparser(L, &z, chunkname); + lua_unlock(L); + return status; +} + + +LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data) { + int status; + TValue *o; + lua_lock(L); + api_checknelems(L, 1); + o = L->top - 1; + if (isLfunction(o)) + status = luaU_dump(L, clvalue(o)->l.p, writer, data, 0); + else + status = 1; + lua_unlock(L); + return status; +} + + +LUA_API int lua_status (lua_State *L) { + return L->status; +} + + +/* +** Garbage-collection function +*/ + +LUA_API int lua_gc (lua_State *L, int what, int data) { + int res = 0; + global_State *g; + lua_lock(L); + g = G(L); + switch (what) { + case LUA_GCSTOP: { + g->GCthreshold = MAX_LUMEM; + break; + } + case LUA_GCRESTART: { + g->GCthreshold = g->totalbytes; + break; + } + case LUA_GCCOLLECT: { + luaC_fullgc(L); + break; + } + case LUA_GCCOUNT: { + /* GC values are expressed in Kbytes: #bytes/2^10 */ + res = cast_int(g->totalbytes >> 10); + break; + } + case LUA_GCCOUNTB: { + res = cast_int(g->totalbytes & 0x3ff); + break; + } + case LUA_GCSTEP: { + lu_mem a = (cast(lu_mem, data) << 10); + if (a <= g->totalbytes) + g->GCthreshold = g->totalbytes - a; + else + g->GCthreshold = 0; + while (g->GCthreshold <= g->totalbytes) + luaC_step(L); + if (g->gcstate == GCSpause) /* end of cycle? */ + res = 1; /* signal it */ + break; + } + case LUA_GCSETPAUSE: { + res = g->gcpause; + g->gcpause = data; + break; + } + case LUA_GCSETSTEPMUL: { + res = g->gcstepmul; + g->gcstepmul = data; + break; + } + default: res = -1; /* invalid option */ + } + lua_unlock(L); + return res; +} + + + +/* +** miscellaneous functions +*/ + + +LUA_API int lua_error (lua_State *L) { + lua_lock(L); + api_checknelems(L, 1); + luaG_errormsg(L); + lua_unlock(L); + return 0; /* to avoid warnings */ +} + + +LUA_API int lua_next (lua_State *L, int idx) { + StkId t; + int more; + lua_lock(L); + t = index2adr(L, idx); + api_check(L, ttistable(t)); + more = luaH_next(L, hvalue(t), L->top - 1); + if (more) { + api_incr_top(L); + } + else /* no more elements */ + L->top -= 1; /* remove key */ + lua_unlock(L); + return more; +} + + +LUA_API void lua_concat (lua_State *L, int n) { + lua_lock(L); + api_checknelems(L, n); + if (n >= 2) { + luaC_checkGC(L); + luaV_concat(L, n, cast_int(L->top - L->base) - 1); + L->top -= (n-1); + } + else if (n == 0) { /* push empty string */ + setsvalue2s(L, L->top, luaS_newlstr(L, "", 0)); + api_incr_top(L); + } + /* else n == 1; nothing to do */ + lua_unlock(L); +} + + +LUA_API lua_Alloc lua_getallocf (lua_State *L, void **ud) { + lua_Alloc f; + lua_lock(L); + if (ud) *ud = G(L)->ud; + f = G(L)->frealloc; + lua_unlock(L); + return f; +} + + +LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { + lua_lock(L); + G(L)->ud = ud; + G(L)->frealloc = f; + lua_unlock(L); +} + + +LUA_API void *lua_newuserdata (lua_State *L, size_t size) { + Udata *u; + lua_lock(L); + luaC_checkGC(L); + u = luaS_newudata(L, size, getcurrenv(L)); + setuvalue(L, L->top, u); + api_incr_top(L); + lua_unlock(L); + return u + 1; +} + + + + +static const char *aux_upvalue (StkId fi, int n, TValue **val) { + Closure *f; + if (!ttisfunction(fi)) return NULL; + f = clvalue(fi); + if (f->c.isC) { + if (!(1 <= n && n <= f->c.nupvalues)) return NULL; + *val = &f->c.upvalue[n-1]; + return ""; + } + else { + Proto *p = f->l.p; + if (!(1 <= n && n <= p->sizeupvalues)) return NULL; + *val = f->l.upvals[n-1]->v; + return getstr(p->upvalues[n-1]); + } +} + + +LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { + const char *name; + TValue *val; + lua_lock(L); + name = aux_upvalue(index2adr(L, funcindex), n, &val); + if (name) { + setobj2s(L, L->top, val); + api_incr_top(L); + } + lua_unlock(L); + return name; +} + + +LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { + const char *name; + TValue *val; + StkId fi; + lua_lock(L); + fi = index2adr(L, funcindex); + api_checknelems(L, 1); + name = aux_upvalue(fi, n, &val); + if (name) { + L->top--; + setobj(L, val, L->top); + luaC_barrier(L, clvalue(fi), L->top); + } + lua_unlock(L); + return name; +} + diff --git a/engines/sword25/util/lua/lapi.h b/engines/sword25/util/lua/lapi.h new file mode 100644 index 0000000000..f968ffc992 --- /dev/null +++ b/engines/sword25/util/lua/lapi.h @@ -0,0 +1,16 @@ +/* +** $Id$ +** Auxiliary functions from Lua API +** See Copyright Notice in lua.h +*/ + +#ifndef lapi_h +#define lapi_h + + +#include "lobject.h" + + +LUAI_FUNC void luaA_pushobject (lua_State *L, const TValue *o); + +#endif diff --git a/engines/sword25/util/lua/lauxlib.cpp b/engines/sword25/util/lua/lauxlib.cpp new file mode 100644 index 0000000000..53c0556625 --- /dev/null +++ b/engines/sword25/util/lua/lauxlib.cpp @@ -0,0 +1,652 @@ +/* +** $Id$ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#include <ctype.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +/* This file uses only the official API of Lua. +** Any function declared here could be written as an application function. +*/ + +#define lauxlib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" + + +#define FREELIST_REF 0 /* free list of references */ + + +/* convert a stack index to positive */ +#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : \ + lua_gettop(L) + (i) + 1) + + +/* +** {====================================================== +** Error-report functions +** ======================================================= +*/ + + +LUALIB_API int luaL_argerror (lua_State *L, int narg, const char *extramsg) { + lua_Debug ar; + if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ + return luaL_error(L, "bad argument #%d (%s)", narg, extramsg); + lua_getinfo(L, "n", &ar); + if (strcmp(ar.namewhat, "method") == 0) { + narg--; /* do not count `self' */ + if (narg == 0) /* error is in the self argument itself? */ + return luaL_error(L, "calling " LUA_QS " on bad self (%s)", + ar.name, extramsg); + } + if (ar.name == NULL) + ar.name = "?"; + return luaL_error(L, "bad argument #%d to " LUA_QS " (%s)", + narg, ar.name, extramsg); +} + + +LUALIB_API int luaL_typerror (lua_State *L, int narg, const char *tname) { + const char *msg = lua_pushfstring(L, "%s expected, got %s", + tname, luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); +} + + +static void tag_error (lua_State *L, int narg, int tag) { + luaL_typerror(L, narg, lua_typename(L, tag)); +} + + +LUALIB_API void luaL_where (lua_State *L, int level) { + lua_Debug ar; + if (lua_getstack(L, level, &ar)) { /* check function at level */ + lua_getinfo(L, "Sl", &ar); /* get info about it */ + if (ar.currentline > 0) { /* is there info? */ + lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline); + return; + } + } + lua_pushliteral(L, ""); /* else, no information available... */ +} + + +LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { + va_list argp; + va_start(argp, fmt); + luaL_where(L, 1); + lua_pushvfstring(L, fmt, argp); + va_end(argp); + lua_concat(L, 2); + return lua_error(L); +} + +/* }====================================================== */ + + +LUALIB_API int luaL_checkoption (lua_State *L, int narg, const char *def, + const char *const lst[]) { + const char *name = (def) ? luaL_optstring(L, narg, def) : + luaL_checkstring(L, narg); + int i; + for (i=0; lst[i]; i++) + if (strcmp(lst[i], name) == 0) + return i; + return luaL_argerror(L, narg, + lua_pushfstring(L, "invalid option " LUA_QS, name)); +} + + +LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { + lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */ + if (!lua_isnil(L, -1)) /* name already in use? */ + return 0; /* leave previous value on top, but return 0 */ + lua_pop(L, 1); + lua_newtable(L); /* create metatable */ + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */ + return 1; +} + + +LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return p; + } + } + } + luaL_typerror(L, ud, tname); /* else error */ + return NULL; /* to avoid warnings */ +} + + +LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *mes) { + if (!lua_checkstack(L, space)) + luaL_error(L, "stack overflow (%s)", mes); +} + + +LUALIB_API void luaL_checktype (lua_State *L, int narg, int t) { + if (lua_type(L, narg) != t) + tag_error(L, narg, t); +} + + +LUALIB_API void luaL_checkany (lua_State *L, int narg) { + if (lua_type(L, narg) == LUA_TNONE) + luaL_argerror(L, narg, "value expected"); +} + + +LUALIB_API const char *luaL_checklstring (lua_State *L, int narg, size_t *len) { + const char *s = lua_tolstring(L, narg, len); + if (!s) tag_error(L, narg, LUA_TSTRING); + return s; +} + + +LUALIB_API const char *luaL_optlstring (lua_State *L, int narg, + const char *def, size_t *len) { + if (lua_isnoneornil(L, narg)) { + if (len) + *len = (def ? strlen(def) : 0); + return def; + } + else return luaL_checklstring(L, narg, len); +} + + +LUALIB_API lua_Number luaL_checknumber (lua_State *L, int narg) { + lua_Number d = lua_tonumber(L, narg); + if (d == 0 && !lua_isnumber(L, narg)) /* avoid extra test when d is not 0 */ + tag_error(L, narg, LUA_TNUMBER); + return d; +} + + +LUALIB_API lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number def) { + return luaL_opt(L, luaL_checknumber, narg, def); +} + + +LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int narg) { + lua_Integer d = lua_tointeger(L, narg); + if (d == 0 && !lua_isnumber(L, narg)) /* avoid extra test when d is not 0 */ + tag_error(L, narg, LUA_TNUMBER); + return d; +} + + +LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int narg, + lua_Integer def) { + return luaL_opt(L, luaL_checkinteger, narg, def); +} + + +LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) { + if (!lua_getmetatable(L, obj)) /* no metatable? */ + return 0; + lua_pushstring(L, event); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 2); /* remove metatable and metafield */ + return 0; + } + else { + lua_remove(L, -2); /* remove only metatable */ + return 1; + } +} + + +LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { + obj = abs_index(L, obj); + if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ + return 0; + lua_pushvalue(L, obj); + lua_call(L, 1, 1); + return 1; +} + + +LUALIB_API void (luaL_register) (lua_State *L, const char *libname, + const luaL_Reg *l) { + luaI_openlib(L, libname, l, 0); +} + + +static int libsize (const luaL_Reg *l) { + int size = 0; + for (; l->name; l++) size++; + return size; +} + + +LUALIB_API void luaI_openlib (lua_State *L, const char *libname, + const luaL_Reg *l, int nup) { + if (libname) { + int size = libsize(l); + /* check whether lib already exists */ + luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); + lua_getfield(L, -1, libname); /* get _LOADED[libname] */ + if (!lua_istable(L, -1)) { /* not found? */ + lua_pop(L, 1); /* remove previous result */ + /* try global variable (and create one if it does not exist) */ + if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL) + luaL_error(L, "name conflict for module " LUA_QS, libname); + lua_pushvalue(L, -1); + lua_setfield(L, -3, libname); /* _LOADED[libname] = new table */ + } + lua_remove(L, -2); /* remove _LOADED table */ + lua_insert(L, -(nup+1)); /* move library table to below upvalues */ + } + for (; l->name; l++) { + int i; + for (i=0; i<nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -nup); + lua_pushcclosure(L, l->func, nup); + lua_setfield(L, -(nup+2), l->name); + } + lua_pop(L, nup); /* remove upvalues */ +} + + + +/* +** {====================================================== +** getn-setn: size for arrays +** ======================================================= +*/ + +#if defined(LUA_COMPAT_GETN) + +static int checkint (lua_State *L, int topop) { + int n = (lua_type(L, -1) == LUA_TNUMBER) ? lua_tointeger(L, -1) : -1; + lua_pop(L, topop); + return n; +} + + +static void getsizes (lua_State *L) { + lua_getfield(L, LUA_REGISTRYINDEX, "LUA_SIZES"); + if (lua_isnil(L, -1)) { /* no `size' table? */ + lua_pop(L, 1); /* remove nil */ + lua_newtable(L); /* create it */ + lua_pushvalue(L, -1); /* `size' will be its own metatable */ + lua_setmetatable(L, -2); + lua_pushliteral(L, "kv"); + lua_setfield(L, -2, "__mode"); /* metatable(N).__mode = "kv" */ + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, "LUA_SIZES"); /* store in register */ + } +} + + +LUALIB_API void luaL_setn (lua_State *L, int t, int n) { + t = abs_index(L, t); + lua_pushliteral(L, "n"); + lua_rawget(L, t); + if (checkint(L, 1) >= 0) { /* is there a numeric field `n'? */ + lua_pushliteral(L, "n"); /* use it */ + lua_pushinteger(L, n); + lua_rawset(L, t); + } + else { /* use `sizes' */ + getsizes(L); + lua_pushvalue(L, t); + lua_pushinteger(L, n); + lua_rawset(L, -3); /* sizes[t] = n */ + lua_pop(L, 1); /* remove `sizes' */ + } +} + + +LUALIB_API int luaL_getn (lua_State *L, int t) { + int n; + t = abs_index(L, t); + lua_pushliteral(L, "n"); /* try t.n */ + lua_rawget(L, t); + if ((n = checkint(L, 1)) >= 0) return n; + getsizes(L); /* else try sizes[t] */ + lua_pushvalue(L, t); + lua_rawget(L, -2); + if ((n = checkint(L, 2)) >= 0) return n; + return (int)lua_objlen(L, t); +} + +#endif + +/* }====================================================== */ + + + +LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, const char *p, + const char *r) { + const char *wild; + size_t l = strlen(p); + luaL_Buffer b; + luaL_buffinit(L, &b); + while ((wild = strstr(s, p)) != NULL) { + luaL_addlstring(&b, s, wild - s); /* push prefix */ + luaL_addstring(&b, r); /* push replacement in place of pattern */ + s = wild + l; /* continue after `p' */ + } + luaL_addstring(&b, s); /* push last suffix */ + luaL_pushresult(&b); + return lua_tostring(L, -1); +} + + +LUALIB_API const char *luaL_findtable (lua_State *L, int idx, + const char *fname, int szhint) { + const char *e; + lua_pushvalue(L, idx); + do { + e = strchr(fname, '.'); + if (e == NULL) e = fname + strlen(fname); + lua_pushlstring(L, fname, e - fname); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { /* no such field? */ + lua_pop(L, 1); /* remove this nil */ + lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */ + lua_pushlstring(L, fname, e - fname); + lua_pushvalue(L, -2); + lua_settable(L, -4); /* set new table into field */ + } + else if (!lua_istable(L, -1)) { /* field has a non-table value? */ + lua_pop(L, 2); /* remove table and value */ + return fname; /* return problematic part of the name */ + } + lua_remove(L, -2); /* remove previous table */ + fname = e + 1; + } while (*e == '.'); + return NULL; +} + + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + + +#define bufflen(B) ((B)->p - (B)->buffer) +#define bufffree(B) ((size_t)(LUAL_BUFFERSIZE - bufflen(B))) + +#define LIMIT (LUA_MINSTACK/2) + + +static int emptybuffer (luaL_Buffer *B) { + size_t l = bufflen(B); + if (l == 0) return 0; /* put nothing on stack */ + else { + lua_pushlstring(B->L, B->buffer, l); + B->p = B->buffer; + B->lvl++; + return 1; + } +} + + +static void adjuststack (luaL_Buffer *B) { + if (B->lvl > 1) { + lua_State *L = B->L; + int toget = 1; /* number of levels to concat */ + size_t toplen = lua_strlen(L, -1); + do { + size_t l = lua_strlen(L, -(toget+1)); + if (B->lvl - toget + 1 >= LIMIT || toplen > l) { + toplen += l; + toget++; + } + else break; + } while (toget < B->lvl); + lua_concat(L, toget); + B->lvl = B->lvl - toget + 1; + } +} + + +LUALIB_API char *luaL_prepbuffer (luaL_Buffer *B) { + if (emptybuffer(B)) + adjuststack(B); + return B->buffer; +} + + +LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { + while (l--) + luaL_addchar(B, *s++); +} + + +LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { + luaL_addlstring(B, s, strlen(s)); +} + + +LUALIB_API void luaL_pushresult (luaL_Buffer *B) { + emptybuffer(B); + lua_concat(B->L, B->lvl); + B->lvl = 1; +} + + +LUALIB_API void luaL_addvalue (luaL_Buffer *B) { + lua_State *L = B->L; + size_t vl; + const char *s = lua_tolstring(L, -1, &vl); + if (vl <= bufffree(B)) { /* fit into buffer? */ + memcpy(B->p, s, vl); /* put it there */ + B->p += vl; + lua_pop(L, 1); /* remove from stack */ + } + else { + if (emptybuffer(B)) + lua_insert(L, -2); /* put buffer before new value */ + B->lvl++; /* add new value into B stack */ + adjuststack(B); + } +} + + +LUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) { + B->L = L; + B->p = B->buffer; + B->lvl = 0; +} + +/* }====================================================== */ + + +LUALIB_API int luaL_ref (lua_State *L, int t) { + int ref; + t = abs_index(L, t); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); /* remove from stack */ + return LUA_REFNIL; /* `nil' has a unique fixed reference */ + } + lua_rawgeti(L, t, FREELIST_REF); /* get first free element */ + ref = (int)lua_tointeger(L, -1); /* ref = t[FREELIST_REF] */ + lua_pop(L, 1); /* remove it from stack */ + if (ref != 0) { /* any free element? */ + lua_rawgeti(L, t, ref); /* remove it from list */ + lua_rawseti(L, t, FREELIST_REF); /* (t[FREELIST_REF] = t[ref]) */ + } + else { /* no free elements */ + ref = (int)lua_objlen(L, t); + ref++; /* create new reference */ + } + lua_rawseti(L, t, ref); + return ref; +} + + +LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { + if (ref >= 0) { + t = abs_index(L, t); + lua_rawgeti(L, t, FREELIST_REF); + lua_rawseti(L, t, ref); /* t[ref] = t[FREELIST_REF] */ + lua_pushinteger(L, ref); + lua_rawseti(L, t, FREELIST_REF); /* t[FREELIST_REF] = ref */ + } +} + + + +/* +** {====================================================== +** Load functions +** ======================================================= +*/ + +typedef struct LoadF { + int extraline; + FILE *f; + char buff[LUAL_BUFFERSIZE]; +} LoadF; + + +static const char *getF (lua_State *L, void *ud, size_t *size) { + LoadF *lf = (LoadF *)ud; + (void)L; + if (lf->extraline) { + lf->extraline = 0; + *size = 1; + return "\n"; + } + if (feof(lf->f)) return NULL; + *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); + return (*size > 0) ? lf->buff : NULL; +} + + +static int errfile (lua_State *L, const char *what, int fnameindex) { + const char *serr = strerror(errno); + const char *filename = lua_tostring(L, fnameindex) + 1; + lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr); + lua_remove(L, fnameindex); + return LUA_ERRFILE; +} + + +LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) { + LoadF lf; + int status, readstatus; + int c; + int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */ + lf.extraline = 0; + if (filename == NULL) { + lua_pushliteral(L, "=stdin"); + lf.f = stdin; + } + else { + lua_pushfstring(L, "@%s", filename); + lf.f = fopen(filename, "r"); + if (lf.f == NULL) return errfile(L, "open", fnameindex); + } + c = getc(lf.f); + if (c == '#') { /* Unix exec. file? */ + lf.extraline = 1; + while ((c = getc(lf.f)) != EOF && c != '\n') ; /* skip first line */ + if (c == '\n') c = getc(lf.f); + } + if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */ + lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ + if (lf.f == NULL) return errfile(L, "reopen", fnameindex); + /* skip eventual `#!...' */ + while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) ; + lf.extraline = 0; + } + ungetc(c, lf.f); + status = lua_load(L, getF, &lf, lua_tostring(L, -1)); + readstatus = ferror(lf.f); + if (filename) fclose(lf.f); /* close file (even in case of errors) */ + if (readstatus) { + lua_settop(L, fnameindex); /* ignore results from `lua_load' */ + return errfile(L, "read", fnameindex); + } + lua_remove(L, fnameindex); + return status; +} + + +typedef struct LoadS { + const char *s; + size_t size; +} LoadS; + + +static const char *getS (lua_State *L, void *ud, size_t *size) { + LoadS *ls = (LoadS *)ud; + (void)L; + if (ls->size == 0) return NULL; + *size = ls->size; + ls->size = 0; + return ls->s; +} + + +LUALIB_API int luaL_loadbuffer (lua_State *L, const char *buff, size_t size, + const char *name) { + LoadS ls; + ls.s = buff; + ls.size = size; + return lua_load(L, getS, &ls, name); +} + + +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s) { + return luaL_loadbuffer(L, s, strlen(s), s); +} + + + +/* }====================================================== */ + + +static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { + (void)ud; + (void)osize; + if (nsize == 0) { + free(ptr); + return NULL; + } + else + return realloc(ptr, nsize); +} + + +static int panic (lua_State *L) { + (void)L; /* to avoid warnings */ + fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", + lua_tostring(L, -1)); + return 0; +} + + +LUALIB_API lua_State *luaL_newstate (void) { + lua_State *L = lua_newstate(l_alloc, NULL); + if (L) lua_atpanic(L, &panic); + return L; +} + diff --git a/engines/sword25/util/lua/lauxlib.h b/engines/sword25/util/lua/lauxlib.h new file mode 100644 index 0000000000..d58f290527 --- /dev/null +++ b/engines/sword25/util/lua/lauxlib.h @@ -0,0 +1,174 @@ +/* +** $Id$ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lauxlib_h +#define lauxlib_h + + +#include <stddef.h> +#include <stdio.h> + +#include "lua.h" + + +#if defined(LUA_COMPAT_GETN) +LUALIB_API int (luaL_getn) (lua_State *L, int t); +LUALIB_API void (luaL_setn) (lua_State *L, int t, int n); +#else +#define luaL_getn(L,i) ((int)lua_objlen(L, i)) +#define luaL_setn(L,i,j) ((void)0) /* no op! */ +#endif + +#if defined(LUA_COMPAT_OPENLIB) +#define luaI_openlib luaL_openlib +#endif + + +/* extra error code for `luaL_load' */ +#define LUA_ERRFILE (LUA_ERRERR+1) + + +typedef struct luaL_Reg { + const char *name; + lua_CFunction func; +} luaL_Reg; + + + +LUALIB_API void (luaI_openlib) (lua_State *L, const char *libname, + const luaL_Reg *l, int nup); +LUALIB_API void (luaL_register) (lua_State *L, const char *libname, + const luaL_Reg *l); +LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_typerror) (lua_State *L, int narg, const char *tname); +LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg, + size_t *l); +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg, + const char *def, size_t *l); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def); + +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg, + lua_Integer def); + +LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); +LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int narg); + +LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); +LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); + +LUALIB_API void (luaL_where) (lua_State *L, int lvl); +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); + +LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def, + const char *const lst[]); + +LUALIB_API int (luaL_ref) (lua_State *L, int t); +LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); + +LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename); +LUALIB_API int (luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz, + const char *name); +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); + +LUALIB_API lua_State *(luaL_newstate) (void); + + +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, + const char *r); + +LUALIB_API const char *(luaL_findtable) (lua_State *L, int idx, + const char *fname, int szhint); + + + + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + +#define luaL_argcheck(L, cond,numarg,extramsg) \ + ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) +#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) + +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) + +#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + + + +typedef struct luaL_Buffer { + char *p; /* current position in buffer */ + int lvl; /* number of strings in the stack (level) */ + lua_State *L; + char buffer[LUAL_BUFFERSIZE]; +} luaL_Buffer; + +#define luaL_addchar(B,c) \ + ((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \ + (*(B)->p++ = (char)(c))) + +/* compatibility only */ +#define luaL_putchar(B,c) luaL_addchar(B,c) + +#define luaL_addsize(B,n) ((B)->p += (n)) + +LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffer) (luaL_Buffer *B); +LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); +LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); + + +/* }====================================================== */ + + +/* compatibility with ref system */ + +/* pre-defined references */ +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) + +#define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \ + (lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0)) + +#define lua_unref(L,ref) luaL_unref(L, LUA_REGISTRYINDEX, (ref)) + +#define lua_getref(L,ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref)) + + +#define luaL_reg luaL_Reg + +#endif + + diff --git a/engines/sword25/util/lua/lbaselib.cpp b/engines/sword25/util/lua/lbaselib.cpp new file mode 100644 index 0000000000..5032e6322a --- /dev/null +++ b/engines/sword25/util/lua/lbaselib.cpp @@ -0,0 +1,654 @@ +/* +** $Id$ +** Basic library +** See Copyright Notice in lua.h +*/ + + + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define lbaselib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + + +/* +** If your system does not support `stdout', you can just remove this function. +** If you need, you can define your own `print' function, following this +** model but changing `fputs' to put the strings at a proper place +** (a console window or a log file, for instance). +*/ +static int luaB_print (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int i; + lua_getglobal(L, "tostring"); + for (i=1; i<=n; i++) { + const char *s; + lua_pushvalue(L, -1); /* function to be called */ + lua_pushvalue(L, i); /* value to print */ + lua_call(L, 1, 1); + s = lua_tostring(L, -1); /* get result */ + if (s == NULL) + return luaL_error(L, LUA_QL("tostring") " must return a string to " + LUA_QL("print")); + lua_pop(L, 1); /* pop result */ + } + return 0; +} + + +static int luaB_tonumber (lua_State *L) { + int base = luaL_optint(L, 2, 10); + if (base == 10) { /* standard conversion */ + luaL_checkany(L, 1); + if (lua_isnumber(L, 1)) { + lua_pushnumber(L, lua_tonumber(L, 1)); + return 1; + } + } + else { + const char *s1 = luaL_checkstring(L, 1); + char *s2; + unsigned long n; + luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); + n = strtoul(s1, &s2, base); + if (s1 != s2) { /* at least one valid digit? */ + while (isspace((unsigned char)(*s2))) s2++; /* skip trailing spaces */ + if (*s2 == '\0') { /* no invalid trailing characters? */ + lua_pushnumber(L, (lua_Number)n); + return 1; + } + } + } + lua_pushnil(L); /* else not a number */ + return 1; +} + + +static int luaB_error (lua_State *L) { + int level = luaL_optint(L, 2, 1); + lua_settop(L, 1); + if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ + luaL_where(L, level); + lua_pushvalue(L, 1); + lua_concat(L, 2); + } + return lua_error(L); +} + + +static int luaB_getmetatable (lua_State *L) { + luaL_checkany(L, 1); + if (!lua_getmetatable(L, 1)) { + lua_pushnil(L); + return 1; /* no metatable */ + } + luaL_getmetafield(L, 1, "__metatable"); + return 1; /* returns either __metatable field (if present) or metatable */ +} + + +static int luaB_setmetatable (lua_State *L) { + int t = lua_type(L, 2); + luaL_checktype(L, 1, LUA_TTABLE); + luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, + "nil or table expected"); + if (luaL_getmetafield(L, 1, "__metatable")) + luaL_error(L, "cannot change a protected metatable"); + lua_settop(L, 2); + lua_setmetatable(L, 1); + return 1; +} + + +static void getfunc (lua_State *L, int opt) { + if (lua_isfunction(L, 1)) lua_pushvalue(L, 1); + else { + lua_Debug ar; + int level = opt ? luaL_optint(L, 1, 1) : luaL_checkint(L, 1); + luaL_argcheck(L, level >= 0, 1, "level must be non-negative"); + if (lua_getstack(L, level, &ar) == 0) + luaL_argerror(L, 1, "invalid level"); + lua_getinfo(L, "f", &ar); + if (lua_isnil(L, -1)) + luaL_error(L, "no function environment for tail call at level %d", + level); + } +} + + +static int luaB_getfenv (lua_State *L) { + getfunc(L, 1); + if (lua_iscfunction(L, -1)) /* is a C function? */ + lua_pushvalue(L, LUA_GLOBALSINDEX); /* return the thread's global env. */ + else + lua_getfenv(L, -1); + return 1; +} + + +static int luaB_setfenv (lua_State *L) { + luaL_checktype(L, 2, LUA_TTABLE); + getfunc(L, 0); + lua_pushvalue(L, 2); + if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0) { + /* change environment of current thread */ + lua_pushthread(L); + lua_insert(L, -2); + lua_setfenv(L, -2); + return 0; + } + else if (lua_iscfunction(L, -2) || lua_setfenv(L, -2) == 0) + luaL_error(L, + LUA_QL("setfenv") " cannot change environment of given object"); + return 1; +} + + +static int luaB_rawequal (lua_State *L) { + luaL_checkany(L, 1); + luaL_checkany(L, 2); + lua_pushboolean(L, lua_rawequal(L, 1, 2)); + return 1; +} + + +static int luaB_rawget (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checkany(L, 2); + lua_settop(L, 2); + lua_rawget(L, 1); + return 1; +} + +static int luaB_rawset (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checkany(L, 2); + luaL_checkany(L, 3); + lua_settop(L, 3); + lua_rawset(L, 1); + return 1; +} + + +static int luaB_gcinfo (lua_State *L) { + lua_pushinteger(L, lua_getgccount(L)); + return 1; +} + + +static int luaB_collectgarbage (lua_State *L) { + static const char *const opts[] = {"stop", "restart", "collect", + "count", "step", "setpause", "setstepmul", NULL}; + static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, + LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL}; + int o = luaL_checkoption(L, 1, "collect", opts); + int ex = luaL_optint(L, 2, 0); + int res = lua_gc(L, optsnum[o], ex); + switch (optsnum[o]) { + case LUA_GCCOUNT: { + int b = lua_gc(L, LUA_GCCOUNTB, 0); + lua_pushnumber(L, res + ((lua_Number)b/1024)); + return 1; + } + case LUA_GCSTEP: { + lua_pushboolean(L, res); + return 1; + } + default: { + lua_pushnumber(L, res); + return 1; + } + } +} + + +static int luaB_type (lua_State *L) { + luaL_checkany(L, 1); + lua_pushstring(L, luaL_typename(L, 1)); + return 1; +} + + +static int luaB_next (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + if (lua_next(L, 1)) + return 2; + else { + lua_pushnil(L); + return 1; + } +} + + +static int luaB_pairs (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */ + lua_pushvalue(L, 1); /* state, */ + lua_pushnil(L); /* and initial value */ + return 3; +} + + +static int ipairsaux (lua_State *L) { + int i = luaL_checkint(L, 2); + luaL_checktype(L, 1, LUA_TTABLE); + i++; /* next value */ + lua_pushinteger(L, i); + lua_rawgeti(L, 1, i); + return (lua_isnil(L, -1)) ? 0 : 2; +} + + +static int luaB_ipairs (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */ + lua_pushvalue(L, 1); /* state, */ + lua_pushinteger(L, 0); /* and initial value */ + return 3; +} + + +static int load_aux (lua_State *L, int status) { + if (status == 0) /* OK? */ + return 1; + else { + lua_pushnil(L); + lua_insert(L, -2); /* put before error message */ + return 2; /* return nil plus error message */ + } +} + + +static int luaB_loadstring (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + const char *chunkname = luaL_optstring(L, 2, s); + return load_aux(L, luaL_loadbuffer(L, s, l, chunkname)); +} + + +static int luaB_loadfile (lua_State *L) { + const char *fname = luaL_optstring(L, 1, NULL); + return load_aux(L, luaL_loadfile(L, fname)); +} + + +/* +** Reader for generic `load' function: `lua_load' uses the +** stack for internal stuff, so the reader cannot change the +** stack top. Instead, it keeps its resulting string in a +** reserved slot inside the stack. +*/ +static const char *generic_reader (lua_State *L, void *ud, size_t *size) { + (void)ud; /* to avoid warnings */ + luaL_checkstack(L, 2, "too many nested functions"); + lua_pushvalue(L, 1); /* get function */ + lua_call(L, 0, 1); /* call it */ + if (lua_isnil(L, -1)) { + *size = 0; + return NULL; + } + else if (lua_isstring(L, -1)) { + lua_replace(L, 3); /* save string in a reserved stack slot */ + return lua_tolstring(L, 3, size); + } + else luaL_error(L, "reader function must return a string"); + return NULL; /* to avoid warnings */ +} + + +static int luaB_load (lua_State *L) { + int status; + const char *cname = luaL_optstring(L, 2, "=(load)"); + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_settop(L, 3); /* function, eventual name, plus one reserved slot */ + status = lua_load(L, generic_reader, NULL, cname); + return load_aux(L, status); +} + + +static int luaB_dofile (lua_State *L) { + const char *fname = luaL_optstring(L, 1, NULL); + int n = lua_gettop(L); + if (luaL_loadfile(L, fname) != 0) lua_error(L); + lua_call(L, 0, LUA_MULTRET); + return lua_gettop(L) - n; +} + + +static int luaB_assert (lua_State *L) { + luaL_checkany(L, 1); + if (!lua_toboolean(L, 1)) + return luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!")); + return lua_gettop(L); +} + + +static int luaB_unpack (lua_State *L) { + int i, e, n; + luaL_checktype(L, 1, LUA_TTABLE); + i = luaL_optint(L, 2, 1); + e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1)); + n = e - i + 1; /* number of elements */ + if (n <= 0) return 0; /* empty range */ + luaL_checkstack(L, n, "table too big to unpack"); + for (; i<=e; i++) /* push arg[i...e] */ + lua_rawgeti(L, 1, i); + return n; +} + + +static int luaB_select (lua_State *L) { + int n = lua_gettop(L); + if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#') { + lua_pushinteger(L, n-1); + return 1; + } + else { + int i = luaL_checkint(L, 1); + if (i < 0) i = n + i; + else if (i > n) i = n; + luaL_argcheck(L, 1 <= i, 1, "index out of range"); + return n - i; + } +} + + +static int luaB_pcall (lua_State *L) { + int status; + luaL_checkany(L, 1); + status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0); + lua_pushboolean(L, (status == 0)); + lua_insert(L, 1); + return lua_gettop(L); /* return status + all results */ +} + + +static int luaB_xpcall (lua_State *L) { + int status; + luaL_checkany(L, 2); + lua_settop(L, 2); + lua_insert(L, 1); /* put error function under function to be called */ + status = lua_pcall(L, 0, LUA_MULTRET, 1); + lua_pushboolean(L, (status == 0)); + lua_replace(L, 1); + return lua_gettop(L); /* return status + all results */ +} + + +static int luaB_tostring (lua_State *L) { + luaL_checkany(L, 1); + if (luaL_callmeta(L, 1, "__tostring")) /* is there a metafield? */ + return 1; /* use its value */ + switch (lua_type(L, 1)) { + case LUA_TNUMBER: + lua_pushstring(L, lua_tostring(L, 1)); + break; + case LUA_TSTRING: + lua_pushvalue(L, 1); + break; + case LUA_TBOOLEAN: + lua_pushstring(L, (lua_toboolean(L, 1) ? "true" : "false")); + break; + case LUA_TNIL: + lua_pushliteral(L, "nil"); + break; + default: + lua_pushfstring(L, "%s: %p", luaL_typename(L, 1), lua_topointer(L, 1)); + break; + } + return 1; +} + + +static int luaB_newproxy (lua_State *L) { + lua_settop(L, 1); + lua_newuserdata(L, 0); /* create proxy */ + if (lua_toboolean(L, 1) == 0) + return 1; /* no metatable */ + else if (lua_isboolean(L, 1)) { + lua_newtable(L); /* create a new metatable `m' ... */ + lua_pushvalue(L, -1); /* ... and mark `m' as a valid metatable */ + lua_pushboolean(L, 1); + lua_rawset(L, lua_upvalueindex(1)); /* weaktable[m] = true */ + } + else { + int validproxy = 0; /* to check if weaktable[metatable(u)] == true */ + if (lua_getmetatable(L, 1)) { + lua_rawget(L, lua_upvalueindex(1)); + validproxy = lua_toboolean(L, -1); + lua_pop(L, 1); /* remove value */ + } + luaL_argcheck(L, validproxy, 1, "boolean or proxy expected"); + lua_getmetatable(L, 1); /* metatable is valid; get it */ + } + lua_setmetatable(L, 2); + return 1; +} + + +static const luaL_Reg base_funcs[] = { + {"assert", luaB_assert}, + {"collectgarbage", luaB_collectgarbage}, + {"dofile", luaB_dofile}, + {"error", luaB_error}, + {"gcinfo", luaB_gcinfo}, + {"getfenv", luaB_getfenv}, + {"getmetatable", luaB_getmetatable}, + {"loadfile", luaB_loadfile}, + {"load", luaB_load}, + {"loadstring", luaB_loadstring}, + {"next", luaB_next}, + {"pcall", luaB_pcall}, + {"print", luaB_print}, + {"rawequal", luaB_rawequal}, + {"rawget", luaB_rawget}, + {"rawset", luaB_rawset}, + {"select", luaB_select}, + {"setfenv", luaB_setfenv}, + {"setmetatable", luaB_setmetatable}, + {"tonumber", luaB_tonumber}, + {"tostring", luaB_tostring}, + {"type", luaB_type}, + {"unpack", luaB_unpack}, + {"xpcall", luaB_xpcall}, + {NULL, NULL} +}; + + +/* +** {====================================================== +** Coroutine library +** ======================================================= +*/ + +#define CO_RUN 0 /* running */ +#define CO_SUS 1 /* suspended */ +#define CO_NOR 2 /* 'normal' (it resumed another coroutine) */ +#define CO_DEAD 3 + +static const char *const statnames[] = + {"running", "suspended", "normal", "dead"}; + +static int costatus (lua_State *L, lua_State *co) { + if (L == co) return CO_RUN; + switch (lua_status(co)) { + case LUA_YIELD: + return CO_SUS; + case 0: { + lua_Debug ar; + if (lua_getstack(co, 0, &ar) > 0) /* does it have frames? */ + return CO_NOR; /* it is running */ + else if (lua_gettop(co) == 0) + return CO_DEAD; + else + return CO_SUS; /* initial state */ + } + default: /* some error occured */ + return CO_DEAD; + } +} + + +static int luaB_costatus (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argcheck(L, co, 1, "coroutine expected"); + lua_pushstring(L, statnames[costatus(L, co)]); + return 1; +} + + +static int auxresume (lua_State *L, lua_State *co, int narg) { + int status = costatus(L, co); + if (!lua_checkstack(co, narg)) + luaL_error(L, "too many arguments to resume"); + if (status != CO_SUS) { + lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]); + return -1; /* error flag */ + } + lua_xmove(L, co, narg); + lua_setlevel(L, co); + status = lua_resume(co, narg); + if (status == 0 || status == LUA_YIELD) { + int nres = lua_gettop(co); + if (!lua_checkstack(L, nres)) + luaL_error(L, "too many results to resume"); + lua_xmove(co, L, nres); /* move yielded values */ + return nres; + } + else { + lua_xmove(co, L, 1); /* move error message */ + return -1; /* error flag */ + } +} + + +static int luaB_coresume (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + int r; + luaL_argcheck(L, co, 1, "coroutine expected"); + r = auxresume(L, co, lua_gettop(L) - 1); + if (r < 0) { + lua_pushboolean(L, 0); + lua_insert(L, -2); + return 2; /* return false + error message */ + } + else { + lua_pushboolean(L, 1); + lua_insert(L, -(r + 1)); + return r + 1; /* return true + `resume' returns */ + } +} + + +static int luaB_auxwrap (lua_State *L) { + lua_State *co = lua_tothread(L, lua_upvalueindex(1)); + int r = auxresume(L, co, lua_gettop(L)); + if (r < 0) { + if (lua_isstring(L, -1)) { /* error object is a string? */ + luaL_where(L, 1); /* add extra info */ + lua_insert(L, -2); + lua_concat(L, 2); + } + lua_error(L); /* propagate error */ + } + return r; +} + + +static int luaB_cocreate (lua_State *L) { + lua_State *NL = lua_newthread(L); + luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), 1, + "Lua function expected"); + lua_pushvalue(L, 1); /* move function to top */ + lua_xmove(L, NL, 1); /* move function from L to NL */ + return 1; +} + + +static int luaB_cowrap (lua_State *L) { + luaB_cocreate(L); + lua_pushcclosure(L, luaB_auxwrap, 1); + return 1; +} + + +static int luaB_yield (lua_State *L) { + return lua_yield(L, lua_gettop(L)); +} + + +static int luaB_corunning (lua_State *L) { + if (lua_pushthread(L)) + lua_pushnil(L); /* main thread is not a coroutine */ + return 1; +} + + +static const luaL_Reg co_funcs[] = { + {"create", luaB_cocreate}, + {"resume", luaB_coresume}, + {"running", luaB_corunning}, + {"status", luaB_costatus}, + {"wrap", luaB_cowrap}, + {"yield", luaB_yield}, + {NULL, NULL} +}; + +/* }====================================================== */ + + +static void auxopen (lua_State *L, const char *name, + lua_CFunction f, lua_CFunction u) { + lua_pushcfunction(L, u); + /* BS25 ==== */ + lua_pushstring(L, name); + lua_pushstring(L, "_next"); + lua_concat(L, 2); + lua_pushvalue(L, -2); + lua_settable(L, LUA_GLOBALSINDEX); + /* ==== BS25 */ + lua_pushcclosure(L, f, 1); + lua_setfield(L, -2, name); +} + + +static void base_open (lua_State *L) { + /* set global _G */ + lua_pushvalue(L, LUA_GLOBALSINDEX); + lua_setglobal(L, "_G"); + /* open lib into global table */ + luaL_register(L, "_G", base_funcs); + lua_pushliteral(L, LUA_VERSION); + lua_setglobal(L, "_VERSION"); /* set global _VERSION */ + /* `ipairs' and `pairs' need auxliliary functions as upvalues */ + auxopen(L, "ipairs", luaB_ipairs, ipairsaux); + auxopen(L, "pairs", luaB_pairs, luaB_next); + /* `newproxy' needs a weaktable as upvalue */ + lua_createtable(L, 0, 1); /* new table `w' */ + lua_pushvalue(L, -1); /* `w' will be its own metatable */ + lua_setmetatable(L, -2); + lua_pushliteral(L, "kv"); + lua_setfield(L, -2, "__mode"); /* metatable(w).__mode = "kv" */ + lua_pushcclosure(L, luaB_newproxy, 1); + lua_setglobal(L, "newproxy"); /* set global `newproxy' */ +} + + +LUALIB_API int luaopen_base (lua_State *L) { + base_open(L); + luaL_register(L, LUA_COLIBNAME, co_funcs); + return 2; +} + diff --git a/engines/sword25/util/lua/lcode.cpp b/engines/sword25/util/lua/lcode.cpp new file mode 100644 index 0000000000..6e7e10017f --- /dev/null +++ b/engines/sword25/util/lua/lcode.cpp @@ -0,0 +1,839 @@ +/* +** $Id$ +** Code generator for Lua +** See Copyright Notice in lua.h +*/ + + +#include <stdlib.h> + +#define lcode_c +#define LUA_CORE + +#include "lua.h" + +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "llex.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "ltable.h" + + +#define hasjumps(e) ((e)->t != (e)->f) + + +static int isnumeral(expdesc *e) { + return (e->k == VKNUM && e->t == NO_JUMP && e->f == NO_JUMP); +} + + +void luaK_nil (FuncState *fs, int from, int n) { + Instruction *previous; + if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ + if (fs->pc == 0) { /* function start? */ + if (from >= fs->nactvar) + return; /* positions are already clean */ + } + else { + previous = &fs->f->code[fs->pc-1]; + if (GET_OPCODE(*previous) == OP_LOADNIL) { + int pfrom = GETARG_A(*previous); + int pto = GETARG_B(*previous); + if (pfrom <= from && from <= pto+1) { /* can connect both? */ + if (from+n-1 > pto) + SETARG_B(*previous, from+n-1); + return; + } + } + } + } + luaK_codeABC(fs, OP_LOADNIL, from, from+n-1, 0); /* else no optimization */ +} + + +int luaK_jump (FuncState *fs) { + int jpc = fs->jpc; /* save list of jumps to here */ + int j; + fs->jpc = NO_JUMP; + j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP); + luaK_concat(fs, &j, jpc); /* keep them on hold */ + return j; +} + + +void luaK_ret (FuncState *fs, int first, int nret) { + luaK_codeABC(fs, OP_RETURN, first, nret+1, 0); +} + + +static int condjump (FuncState *fs, OpCode op, int A, int B, int C) { + luaK_codeABC(fs, op, A, B, C); + return luaK_jump(fs); +} + + +static void fixjump (FuncState *fs, int pc, int dest) { + Instruction *jmp = &fs->f->code[pc]; + int offset = dest-(pc+1); + lua_assert(dest != NO_JUMP); + if (abs(offset) > MAXARG_sBx) + luaX_syntaxerror(fs->ls, "control structure too long"); + SETARG_sBx(*jmp, offset); +} + + +/* +** returns current `pc' and marks it as a jump target (to avoid wrong +** optimizations with consecutive instructions not in the same basic block). +*/ +int luaK_getlabel (FuncState *fs) { + fs->lasttarget = fs->pc; + return fs->pc; +} + + +static int getjump (FuncState *fs, int pc) { + int offset = GETARG_sBx(fs->f->code[pc]); + if (offset == NO_JUMP) /* point to itself represents end of list */ + return NO_JUMP; /* end of list */ + else + return (pc+1)+offset; /* turn offset into absolute position */ +} + + +static Instruction *getjumpcontrol (FuncState *fs, int pc) { + Instruction *pi = &fs->f->code[pc]; + if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) + return pi-1; + else + return pi; +} + + +/* +** check whether list has any jump that do not produce a value +** (or produce an inverted value) +*/ +static int need_value (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) { + Instruction i = *getjumpcontrol(fs, list); + if (GET_OPCODE(i) != OP_TESTSET) return 1; + } + return 0; /* not found */ +} + + +static int patchtestreg (FuncState *fs, int node, int reg) { + Instruction *i = getjumpcontrol(fs, node); + if (GET_OPCODE(*i) != OP_TESTSET) + return 0; /* cannot patch other instructions */ + if (reg != NO_REG && reg != GETARG_B(*i)) + SETARG_A(*i, reg); + else /* no register to put value or register already has the value */ + *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); + + return 1; +} + + +static void removevalues (FuncState *fs, int list) { + for (; list != NO_JUMP; list = getjump(fs, list)) + patchtestreg(fs, list, NO_REG); +} + + +static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, + int dtarget) { + while (list != NO_JUMP) { + int next = getjump(fs, list); + if (patchtestreg(fs, list, reg)) + fixjump(fs, list, vtarget); + else + fixjump(fs, list, dtarget); /* jump to default target */ + list = next; + } +} + + +static void dischargejpc (FuncState *fs) { + patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc); + fs->jpc = NO_JUMP; +} + + +void luaK_patchlist (FuncState *fs, int list, int target) { + if (target == fs->pc) + luaK_patchtohere(fs, list); + else { + lua_assert(target < fs->pc); + patchlistaux(fs, list, target, NO_REG, target); + } +} + + +void luaK_patchtohere (FuncState *fs, int list) { + luaK_getlabel(fs); + luaK_concat(fs, &fs->jpc, list); +} + + +void luaK_concat (FuncState *fs, int *l1, int l2) { + if (l2 == NO_JUMP) return; + else if (*l1 == NO_JUMP) + *l1 = l2; + else { + int list = *l1; + int next; + while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ + list = next; + fixjump(fs, list, l2); + } +} + + +void luaK_checkstack (FuncState *fs, int n) { + int newstack = fs->freereg + n; + if (newstack > fs->f->maxstacksize) { + if (newstack >= MAXSTACK) + luaX_syntaxerror(fs->ls, "function or expression too complex"); + fs->f->maxstacksize = cast_byte(newstack); + } +} + + +void luaK_reserveregs (FuncState *fs, int n) { + luaK_checkstack(fs, n); + fs->freereg += n; +} + + +static void freereg (FuncState *fs, int reg) { + if (!ISK(reg) && reg >= fs->nactvar) { + fs->freereg--; + lua_assert(reg == fs->freereg); + } +} + + +static void freeexp (FuncState *fs, expdesc *e) { + if (e->k == VNONRELOC) + freereg(fs, e->u.s.info); +} + + +static int addk (FuncState *fs, TValue *k, TValue *v) { + lua_State *L = fs->L; + TValue *idx = luaH_set(L, fs->h, k); + Proto *f = fs->f; + int oldsize = f->sizek; + if (ttisnumber(idx)) { + lua_assert(luaO_rawequalObj(&fs->f->k[cast_int(nvalue(idx))], v)); + return cast_int(nvalue(idx)); + } + else { /* constant not found; create a new entry */ + setnvalue(idx, cast_num(fs->nk)); + luaM_growvector(L, f->k, fs->nk, f->sizek, TValue, + MAXARG_Bx, "constant table overflow"); + while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); + setobj(L, &f->k[fs->nk], v); + luaC_barrier(L, f, v); + return fs->nk++; + } +} + + +int luaK_stringK (FuncState *fs, TString *s) { + TValue o; + setsvalue(fs->L, &o, s); + return addk(fs, &o, &o); +} + + +int luaK_numberK (FuncState *fs, lua_Number r) { + TValue o; + setnvalue(&o, r); + return addk(fs, &o, &o); +} + + +static int boolK (FuncState *fs, int b) { + TValue o; + setbvalue(&o, b); + return addk(fs, &o, &o); +} + + +static int nilK (FuncState *fs) { + TValue k, v; + setnilvalue(&v); + /* cannot use nil as key; instead use table itself to represent nil */ + sethvalue(fs->L, &k, fs->h); + return addk(fs, &k, &v); +} + + +void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { + if (e->k == VCALL) { /* expression is an open function call? */ + SETARG_C(getcode(fs, e), nresults+1); + } + else if (e->k == VVARARG) { + SETARG_B(getcode(fs, e), nresults+1); + SETARG_A(getcode(fs, e), fs->freereg); + luaK_reserveregs(fs, 1); + } +} + + +void luaK_setoneret (FuncState *fs, expdesc *e) { + if (e->k == VCALL) { /* expression is an open function call? */ + e->k = VNONRELOC; + e->u.s.info = GETARG_A(getcode(fs, e)); + } + else if (e->k == VVARARG) { + SETARG_B(getcode(fs, e), 2); + e->k = VRELOCABLE; /* can relocate its simple result */ + } +} + + +void luaK_dischargevars (FuncState *fs, expdesc *e) { + switch (e->k) { + case VLOCAL: { + e->k = VNONRELOC; + break; + } + case VUPVAL: { + e->u.s.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.s.info, 0); + e->k = VRELOCABLE; + break; + } + case VGLOBAL: { + e->u.s.info = luaK_codeABx(fs, OP_GETGLOBAL, 0, e->u.s.info); + e->k = VRELOCABLE; + break; + } + case VINDEXED: { + freereg(fs, e->u.s.aux); + freereg(fs, e->u.s.info); + e->u.s.info = luaK_codeABC(fs, OP_GETTABLE, 0, e->u.s.info, e->u.s.aux); + e->k = VRELOCABLE; + break; + } + case VVARARG: + case VCALL: { + luaK_setoneret(fs, e); + break; + } + default: break; /* there is one value available (somewhere) */ + } +} + + +static int code_label (FuncState *fs, int A, int b, int jump) { + luaK_getlabel(fs); /* those instructions may be jump targets */ + return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); +} + + +static void discharge2reg (FuncState *fs, expdesc *e, int reg) { + luaK_dischargevars(fs, e); + switch (e->k) { + case VNIL: { + luaK_nil(fs, reg, 1); + break; + } + case VFALSE: case VTRUE: { + luaK_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, 0); + break; + } + case VK: { + luaK_codeABx(fs, OP_LOADK, reg, e->u.s.info); + break; + } + case VKNUM: { + luaK_codeABx(fs, OP_LOADK, reg, luaK_numberK(fs, e->u.nval)); + break; + } + case VRELOCABLE: { + Instruction *pc = &getcode(fs, e); + SETARG_A(*pc, reg); + break; + } + case VNONRELOC: { + if (reg != e->u.s.info) + luaK_codeABC(fs, OP_MOVE, reg, e->u.s.info, 0); + break; + } + default: { + lua_assert(e->k == VVOID || e->k == VJMP); + return; /* nothing to do... */ + } + } + e->u.s.info = reg; + e->k = VNONRELOC; +} + + +static void discharge2anyreg (FuncState *fs, expdesc *e) { + if (e->k != VNONRELOC) { + luaK_reserveregs(fs, 1); + discharge2reg(fs, e, fs->freereg-1); + } +} + + +static void exp2reg (FuncState *fs, expdesc *e, int reg) { + discharge2reg(fs, e, reg); + if (e->k == VJMP) + luaK_concat(fs, &e->t, e->u.s.info); /* put this jump in `t' list */ + if (hasjumps(e)) { + int final; /* position after whole expression */ + int p_f = NO_JUMP; /* position of an eventual LOAD false */ + int p_t = NO_JUMP; /* position of an eventual LOAD true */ + if (need_value(fs, e->t) || need_value(fs, e->f)) { + int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); + p_f = code_label(fs, reg, 0, 1); + p_t = code_label(fs, reg, 1, 0); + luaK_patchtohere(fs, fj); + } + final = luaK_getlabel(fs); + patchlistaux(fs, e->f, final, reg, p_f); + patchlistaux(fs, e->t, final, reg, p_t); + } + e->f = e->t = NO_JUMP; + e->u.s.info = reg; + e->k = VNONRELOC; +} + + +void luaK_exp2nextreg (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + freeexp(fs, e); + luaK_reserveregs(fs, 1); + exp2reg(fs, e, fs->freereg - 1); +} + + +int luaK_exp2anyreg (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + if (e->k == VNONRELOC) { + if (!hasjumps(e)) return e->u.s.info; /* exp is already in a register */ + if (e->u.s.info >= fs->nactvar) { /* reg. is not a local? */ + exp2reg(fs, e, e->u.s.info); /* put value on it */ + return e->u.s.info; + } + } + luaK_exp2nextreg(fs, e); /* default */ + return e->u.s.info; +} + + +void luaK_exp2val (FuncState *fs, expdesc *e) { + if (hasjumps(e)) + luaK_exp2anyreg(fs, e); + else + luaK_dischargevars(fs, e); +} + + +int luaK_exp2RK (FuncState *fs, expdesc *e) { + luaK_exp2val(fs, e); + switch (e->k) { + case VKNUM: + case VTRUE: + case VFALSE: + case VNIL: { + if (fs->nk <= MAXINDEXRK) { /* constant fit in RK operand? */ + e->u.s.info = (e->k == VNIL) ? nilK(fs) : + (e->k == VKNUM) ? luaK_numberK(fs, e->u.nval) : + boolK(fs, (e->k == VTRUE)); + e->k = VK; + return RKASK(e->u.s.info); + } + else break; + } + case VK: { + if (e->u.s.info <= MAXINDEXRK) /* constant fit in argC? */ + return RKASK(e->u.s.info); + else break; + } + default: break; + } + /* not a constant in the right range: put it in a register */ + return luaK_exp2anyreg(fs, e); +} + + +void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { + switch (var->k) { + case VLOCAL: { + freeexp(fs, ex); + exp2reg(fs, ex, var->u.s.info); + return; + } + case VUPVAL: { + int e = luaK_exp2anyreg(fs, ex); + luaK_codeABC(fs, OP_SETUPVAL, e, var->u.s.info, 0); + break; + } + case VGLOBAL: { + int e = luaK_exp2anyreg(fs, ex); + luaK_codeABx(fs, OP_SETGLOBAL, e, var->u.s.info); + break; + } + case VINDEXED: { + int e = luaK_exp2RK(fs, ex); + luaK_codeABC(fs, OP_SETTABLE, var->u.s.info, var->u.s.aux, e); + break; + } + default: { + lua_assert(0); /* invalid var kind to store */ + break; + } + } + freeexp(fs, ex); +} + + +void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { + int func; + luaK_exp2anyreg(fs, e); + freeexp(fs, e); + func = fs->freereg; + luaK_reserveregs(fs, 2); + luaK_codeABC(fs, OP_SELF, func, e->u.s.info, luaK_exp2RK(fs, key)); + freeexp(fs, key); + e->u.s.info = func; + e->k = VNONRELOC; +} + + +static void invertjump (FuncState *fs, expdesc *e) { + Instruction *pc = getjumpcontrol(fs, e->u.s.info); + lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && + GET_OPCODE(*pc) != OP_TEST); + SETARG_A(*pc, !(GETARG_A(*pc))); +} + + +static int jumponcond (FuncState *fs, expdesc *e, int cond) { + if (e->k == VRELOCABLE) { + Instruction ie = getcode(fs, e); + if (GET_OPCODE(ie) == OP_NOT) { + fs->pc--; /* remove previous OP_NOT */ + return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond); + } + /* else go through */ + } + discharge2anyreg(fs, e); + freeexp(fs, e); + return condjump(fs, OP_TESTSET, NO_REG, e->u.s.info, cond); +} + + +void luaK_goiftrue (FuncState *fs, expdesc *e) { + int pc; /* pc of last jump */ + luaK_dischargevars(fs, e); + switch (e->k) { + case VK: case VKNUM: case VTRUE: { + pc = NO_JUMP; /* always true; do nothing */ + break; + } + case VFALSE: { + pc = luaK_jump(fs); /* always jump */ + break; + } + case VJMP: { + invertjump(fs, e); + pc = e->u.s.info; + break; + } + default: { + pc = jumponcond(fs, e, 0); + break; + } + } + luaK_concat(fs, &e->f, pc); /* insert last jump in `f' list */ + luaK_patchtohere(fs, e->t); + e->t = NO_JUMP; +} + + +static void luaK_goiffalse (FuncState *fs, expdesc *e) { + int pc; /* pc of last jump */ + luaK_dischargevars(fs, e); + switch (e->k) { + case VNIL: case VFALSE: { + pc = NO_JUMP; /* always false; do nothing */ + break; + } + case VTRUE: { + pc = luaK_jump(fs); /* always jump */ + break; + } + case VJMP: { + pc = e->u.s.info; + break; + } + default: { + pc = jumponcond(fs, e, 1); + break; + } + } + luaK_concat(fs, &e->t, pc); /* insert last jump in `t' list */ + luaK_patchtohere(fs, e->f); + e->f = NO_JUMP; +} + + +static void codenot (FuncState *fs, expdesc *e) { + luaK_dischargevars(fs, e); + switch (e->k) { + case VNIL: case VFALSE: { + e->k = VTRUE; + break; + } + case VK: case VKNUM: case VTRUE: { + e->k = VFALSE; + break; + } + case VJMP: { + invertjump(fs, e); + break; + } + case VRELOCABLE: + case VNONRELOC: { + discharge2anyreg(fs, e); + freeexp(fs, e); + e->u.s.info = luaK_codeABC(fs, OP_NOT, 0, e->u.s.info, 0); + e->k = VRELOCABLE; + break; + } + default: { + lua_assert(0); /* cannot happen */ + break; + } + } + /* interchange true and false lists */ + { int temp = e->f; e->f = e->t; e->t = temp; } + removevalues(fs, e->f); + removevalues(fs, e->t); +} + + +void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { + t->u.s.aux = luaK_exp2RK(fs, k); + t->k = VINDEXED; +} + + +static int constfolding (OpCode op, expdesc *e1, expdesc *e2) { + lua_Number v1, v2, r; + if (!isnumeral(e1) || !isnumeral(e2)) return 0; + v1 = e1->u.nval; + v2 = e2->u.nval; + switch (op) { + case OP_ADD: r = luai_numadd(v1, v2); break; + case OP_SUB: r = luai_numsub(v1, v2); break; + case OP_MUL: r = luai_nummul(v1, v2); break; + case OP_DIV: + if (v2 == 0) return 0; /* do not attempt to divide by 0 */ + r = luai_numdiv(v1, v2); break; + case OP_MOD: + if (v2 == 0) return 0; /* do not attempt to divide by 0 */ + r = luai_nummod(v1, v2); break; + case OP_POW: r = luai_numpow(v1, v2); break; + case OP_UNM: r = luai_numunm(v1); break; + case OP_LEN: return 0; /* no constant folding for 'len' */ + default: lua_assert(0); r = 0; break; + } + if (luai_numisnan(r)) return 0; /* do not attempt to produce NaN */ + e1->u.nval = r; + return 1; +} + + +static void codearith (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) { + if (constfolding(op, e1, e2)) + return; + else { + int o2 = (op != OP_UNM && op != OP_LEN) ? luaK_exp2RK(fs, e2) : 0; + int o1 = luaK_exp2RK(fs, e1); + if (o1 > o2) { + freeexp(fs, e1); + freeexp(fs, e2); + } + else { + freeexp(fs, e2); + freeexp(fs, e1); + } + e1->u.s.info = luaK_codeABC(fs, op, 0, o1, o2); + e1->k = VRELOCABLE; + } +} + + +static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1, + expdesc *e2) { + int o1 = luaK_exp2RK(fs, e1); + int o2 = luaK_exp2RK(fs, e2); + freeexp(fs, e2); + freeexp(fs, e1); + if (cond == 0 && op != OP_EQ) { + int temp; /* exchange args to replace by `<' or `<=' */ + temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */ + cond = 1; + } + e1->u.s.info = condjump(fs, op, cond, o1, o2); + e1->k = VJMP; +} + + +void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e) { + expdesc e2; + e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0; + switch (op) { + case OPR_MINUS: { + if (!isnumeral(e)) + luaK_exp2anyreg(fs, e); /* cannot operate on non-numeric constants */ + codearith(fs, OP_UNM, e, &e2); + break; + } + case OPR_NOT: codenot(fs, e); break; + case OPR_LEN: { + luaK_exp2anyreg(fs, e); /* cannot operate on constants */ + codearith(fs, OP_LEN, e, &e2); + break; + } + default: lua_assert(0); + } +} + + +void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { + switch (op) { + case OPR_AND: { + luaK_goiftrue(fs, v); + break; + } + case OPR_OR: { + luaK_goiffalse(fs, v); + break; + } + case OPR_CONCAT: { + luaK_exp2nextreg(fs, v); /* operand must be on the `stack' */ + break; + } + case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: + case OPR_MOD: case OPR_POW: { + if (!isnumeral(v)) luaK_exp2RK(fs, v); + break; + } + default: { + luaK_exp2RK(fs, v); + break; + } + } +} + + +void luaK_posfix (FuncState *fs, BinOpr op, expdesc *e1, expdesc *e2) { + switch (op) { + case OPR_AND: { + lua_assert(e1->t == NO_JUMP); /* list must be closed */ + luaK_dischargevars(fs, e2); + luaK_concat(fs, &e2->f, e1->f); + *e1 = *e2; + break; + } + case OPR_OR: { + lua_assert(e1->f == NO_JUMP); /* list must be closed */ + luaK_dischargevars(fs, e2); + luaK_concat(fs, &e2->t, e1->t); + *e1 = *e2; + break; + } + case OPR_CONCAT: { + luaK_exp2val(fs, e2); + if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) { + lua_assert(e1->u.s.info == GETARG_B(getcode(fs, e2))-1); + freeexp(fs, e1); + SETARG_B(getcode(fs, e2), e1->u.s.info); + e1->k = VRELOCABLE; e1->u.s.info = e2->u.s.info; + } + else { + luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ + codearith(fs, OP_CONCAT, e1, e2); + } + break; + } + case OPR_ADD: codearith(fs, OP_ADD, e1, e2); break; + case OPR_SUB: codearith(fs, OP_SUB, e1, e2); break; + case OPR_MUL: codearith(fs, OP_MUL, e1, e2); break; + case OPR_DIV: codearith(fs, OP_DIV, e1, e2); break; + case OPR_MOD: codearith(fs, OP_MOD, e1, e2); break; + case OPR_POW: codearith(fs, OP_POW, e1, e2); break; + case OPR_EQ: codecomp(fs, OP_EQ, 1, e1, e2); break; + case OPR_NE: codecomp(fs, OP_EQ, 0, e1, e2); break; + case OPR_LT: codecomp(fs, OP_LT, 1, e1, e2); break; + case OPR_LE: codecomp(fs, OP_LE, 1, e1, e2); break; + case OPR_GT: codecomp(fs, OP_LT, 0, e1, e2); break; + case OPR_GE: codecomp(fs, OP_LE, 0, e1, e2); break; + default: lua_assert(0); + } +} + + +void luaK_fixline (FuncState *fs, int line) { + fs->f->lineinfo[fs->pc - 1] = line; +} + + +static int luaK_code (FuncState *fs, Instruction i, int line) { + Proto *f = fs->f; + dischargejpc(fs); /* `pc' will change */ + /* put new instruction in code array */ + luaM_growvector(fs->L, f->code, fs->pc, f->sizecode, Instruction, + MAX_INT, "code size overflow"); + f->code[fs->pc] = i; + /* save corresponding line information */ + luaM_growvector(fs->L, f->lineinfo, fs->pc, f->sizelineinfo, int, + MAX_INT, "code size overflow"); + f->lineinfo[fs->pc] = line; + return fs->pc++; +} + + +int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { + lua_assert(getOpMode(o) == iABC); + lua_assert(getBMode(o) != OpArgN || b == 0); + lua_assert(getCMode(o) != OpArgN || c == 0); + return luaK_code(fs, CREATE_ABC(o, a, b, c), fs->ls->lastline); +} + + +int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { + lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); + lua_assert(getCMode(o) == OpArgN); + return luaK_code(fs, CREATE_ABx(o, a, bc), fs->ls->lastline); +} + + +void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { + int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1; + int b = (tostore == LUA_MULTRET) ? 0 : tostore; + lua_assert(tostore != 0); + if (c <= MAXARG_C) + luaK_codeABC(fs, OP_SETLIST, base, b, c); + else { + luaK_codeABC(fs, OP_SETLIST, base, b, 0); + luaK_code(fs, cast(Instruction, c), fs->ls->lastline); + } + fs->freereg = base + 1; /* free registers with list values */ +} + diff --git a/engines/sword25/util/lua/lcode.h b/engines/sword25/util/lua/lcode.h new file mode 100644 index 0000000000..751b2b5695 --- /dev/null +++ b/engines/sword25/util/lua/lcode.h @@ -0,0 +1,76 @@ +/* +** $Id$ +** Code generator for Lua +** See Copyright Notice in lua.h +*/ + +#ifndef lcode_h +#define lcode_h + +#include "llex.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" + + +/* +** Marks the end of a patch list. It is an invalid value both as an absolute +** address, and as a list link (would link an element to itself). +*/ +#define NO_JUMP (-1) + + +/* +** grep "ORDER OPR" if you change these enums +*/ +typedef enum BinOpr { + OPR_ADD, OPR_SUB, OPR_MUL, OPR_DIV, OPR_MOD, OPR_POW, + OPR_CONCAT, + OPR_NE, OPR_EQ, + OPR_LT, OPR_LE, OPR_GT, OPR_GE, + OPR_AND, OPR_OR, + OPR_NOBINOPR +} BinOpr; + + +typedef enum UnOpr { OPR_MINUS, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; + + +#define getcode(fs,e) ((fs)->f->code[(e)->u.s.info]) + +#define luaK_codeAsBx(fs,o,A,sBx) luaK_codeABx(fs,o,A,(sBx)+MAXARG_sBx) + +#define luaK_setmultret(fs,e) luaK_setreturns(fs, e, LUA_MULTRET) + +LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx); +LUAI_FUNC int luaK_codeABC (FuncState *fs, OpCode o, int A, int B, int C); +LUAI_FUNC void luaK_fixline (FuncState *fs, int line); +LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); +LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); +LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); +LUAI_FUNC int luaK_stringK (FuncState *fs, TString *s); +LUAI_FUNC int luaK_numberK (FuncState *fs, lua_Number r); +LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key); +LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k); +LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e); +LUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e); +LUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults); +LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e); +LUAI_FUNC int luaK_jump (FuncState *fs); +LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret); +LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target); +LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list); +LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2); +LUAI_FUNC int luaK_getlabel (FuncState *fs); +LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v); +LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v); +LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1, expdesc *v2); +LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore); + + +#endif diff --git a/engines/sword25/util/lua/ldblib.cpp b/engines/sword25/util/lua/ldblib.cpp new file mode 100644 index 0000000000..b2e249e9b7 --- /dev/null +++ b/engines/sword25/util/lua/ldblib.cpp @@ -0,0 +1,397 @@ +/* +** $Id$ +** Interface from Lua to its debug API +** See Copyright Notice in lua.h +*/ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define ldblib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + + +static int db_getregistry (lua_State *L) { + lua_pushvalue(L, LUA_REGISTRYINDEX); + return 1; +} + + +static int db_getmetatable (lua_State *L) { + luaL_checkany(L, 1); + if (!lua_getmetatable(L, 1)) { + lua_pushnil(L); /* no metatable */ + } + return 1; +} + + +static int db_setmetatable (lua_State *L) { + int t = lua_type(L, 2); + luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, + "nil or table expected"); + lua_settop(L, 2); + lua_pushboolean(L, lua_setmetatable(L, 1)); + return 1; +} + + +static int db_getfenv (lua_State *L) { + lua_getfenv(L, 1); + return 1; +} + + +static int db_setfenv (lua_State *L) { + luaL_checktype(L, 2, LUA_TTABLE); + lua_settop(L, 2); + if (lua_setfenv(L, 1) == 0) + luaL_error(L, LUA_QL("setfenv") + " cannot change environment of given object"); + return 1; +} + + +static void settabss (lua_State *L, const char *i, const char *v) { + lua_pushstring(L, v); + lua_setfield(L, -2, i); +} + + +static void settabsi (lua_State *L, const char *i, int v) { + lua_pushinteger(L, v); + lua_setfield(L, -2, i); +} + + +static lua_State *getthread (lua_State *L, int *arg) { + if (lua_isthread(L, 1)) { + *arg = 1; + return lua_tothread(L, 1); + } + else { + *arg = 0; + return L; + } +} + + +static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) { + if (L == L1) { + lua_pushvalue(L, -2); + lua_remove(L, -3); + } + else + lua_xmove(L1, L, 1); + lua_setfield(L, -2, fname); +} + + +static int db_getinfo (lua_State *L) { + lua_Debug ar; + int arg; + lua_State *L1 = getthread(L, &arg); + const char *options = luaL_optstring(L, arg+2, "flnSu"); + if (lua_isnumber(L, arg+1)) { + if (!lua_getstack(L1, (int)lua_tointeger(L, arg+1), &ar)) { + lua_pushnil(L); /* level out of range */ + return 1; + } + } + else if (lua_isfunction(L, arg+1)) { + lua_pushfstring(L, ">%s", options); + options = lua_tostring(L, -1); + lua_pushvalue(L, arg+1); + lua_xmove(L, L1, 1); + } + else + return luaL_argerror(L, arg+1, "function or level expected"); + if (!lua_getinfo(L1, options, &ar)) + return luaL_argerror(L, arg+2, "invalid option"); + lua_createtable(L, 0, 2); + if (strchr(options, 'S')) { + settabss(L, "source", ar.source); + settabss(L, "short_src", ar.short_src); + settabsi(L, "linedefined", ar.linedefined); + settabsi(L, "lastlinedefined", ar.lastlinedefined); + settabss(L, "what", ar.what); + } + if (strchr(options, 'l')) + settabsi(L, "currentline", ar.currentline); + if (strchr(options, 'u')) + settabsi(L, "nups", ar.nups); + if (strchr(options, 'n')) { + settabss(L, "name", ar.name); + settabss(L, "namewhat", ar.namewhat); + } + if (strchr(options, 'L')) + treatstackoption(L, L1, "activelines"); + if (strchr(options, 'f')) + treatstackoption(L, L1, "func"); + return 1; /* return table */ +} + + +static int db_getlocal (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + lua_Debug ar; + const char *name; + if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ + return luaL_argerror(L, arg+1, "level out of range"); + name = lua_getlocal(L1, &ar, luaL_checkint(L, arg+2)); + if (name) { + lua_xmove(L1, L, 1); + lua_pushstring(L, name); + lua_pushvalue(L, -2); + return 2; + } + else { + lua_pushnil(L); + return 1; + } +} + + +static int db_setlocal (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + lua_Debug ar; + if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ + return luaL_argerror(L, arg+1, "level out of range"); + luaL_checkany(L, arg+3); + lua_settop(L, arg+3); + lua_xmove(L, L1, 1); + lua_pushstring(L, lua_setlocal(L1, &ar, luaL_checkint(L, arg+2))); + return 1; +} + + +static int auxupvalue (lua_State *L, int get) { + const char *name; + int n = luaL_checkint(L, 2); + luaL_checktype(L, 1, LUA_TFUNCTION); + if (lua_iscfunction(L, 1)) return 0; /* cannot touch C upvalues from Lua */ + name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n); + if (name == NULL) return 0; + lua_pushstring(L, name); + lua_insert(L, -(get+1)); + return get + 1; +} + + +static int db_getupvalue (lua_State *L) { + return auxupvalue(L, 1); +} + + +static int db_setupvalue (lua_State *L) { + luaL_checkany(L, 3); + return auxupvalue(L, 0); +} + + + +static const char KEY_HOOK = 'h'; + + +static void hookf (lua_State *L, lua_Debug *ar) { + static const char *const hooknames[] = + {"call", "return", "line", "count", "tail return"}; + lua_pushlightuserdata(L, (void *)&KEY_HOOK); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_pushlightuserdata(L, L); + lua_rawget(L, -2); + if (lua_isfunction(L, -1)) { + lua_pushstring(L, hooknames[(int)ar->event]); + if (ar->currentline >= 0) + lua_pushinteger(L, ar->currentline); + else lua_pushnil(L); + lua_assert(lua_getinfo(L, "lS", ar)); + lua_call(L, 2, 0); + } +} + + +static int makemask (const char *smask, int count) { + int mask = 0; + if (strchr(smask, 'c')) mask |= LUA_MASKCALL; + if (strchr(smask, 'r')) mask |= LUA_MASKRET; + if (strchr(smask, 'l')) mask |= LUA_MASKLINE; + if (count > 0) mask |= LUA_MASKCOUNT; + return mask; +} + + +static char *unmakemask (int mask, char *smask) { + int i = 0; + if (mask & LUA_MASKCALL) smask[i++] = 'c'; + if (mask & LUA_MASKRET) smask[i++] = 'r'; + if (mask & LUA_MASKLINE) smask[i++] = 'l'; + smask[i] = '\0'; + return smask; +} + + +static void gethooktable (lua_State *L) { + lua_pushlightuserdata(L, (void *)&KEY_HOOK); + lua_rawget(L, LUA_REGISTRYINDEX); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + lua_createtable(L, 0, 1); + lua_pushlightuserdata(L, (void *)&KEY_HOOK); + lua_pushvalue(L, -2); + lua_rawset(L, LUA_REGISTRYINDEX); + } +} + + +static int db_sethook (lua_State *L) { + int arg, mask, count; + lua_Hook func; + lua_State *L1 = getthread(L, &arg); + if (lua_isnoneornil(L, arg+1)) { + lua_settop(L, arg+1); + func = NULL; mask = 0; count = 0; /* turn off hooks */ + } + else { + const char *smask = luaL_checkstring(L, arg+2); + luaL_checktype(L, arg+1, LUA_TFUNCTION); + count = luaL_optint(L, arg+3, 0); + func = hookf; mask = makemask(smask, count); + } + gethooktable(L); + lua_pushlightuserdata(L, L1); + lua_pushvalue(L, arg+1); + lua_rawset(L, -3); /* set new hook */ + lua_pop(L, 1); /* remove hook table */ + lua_sethook(L1, func, mask, count); /* set hooks */ + return 0; +} + + +static int db_gethook (lua_State *L) { + int arg; + lua_State *L1 = getthread(L, &arg); + char buff[5]; + int mask = lua_gethookmask(L1); + lua_Hook hook = lua_gethook(L1); + if (hook != NULL && hook != hookf) /* external hook? */ + lua_pushliteral(L, "external hook"); + else { + gethooktable(L); + lua_pushlightuserdata(L, L1); + lua_rawget(L, -2); /* get hook */ + lua_remove(L, -2); /* remove hook table */ + } + lua_pushstring(L, unmakemask(mask, buff)); + lua_pushinteger(L, lua_gethookcount(L1)); + return 3; +} + + +static int db_debug (lua_State *L) { + for (;;) { + char buffer[250]; + fputs("lua_debug> ", stderr); + if (fgets(buffer, sizeof(buffer), stdin) == 0 || + strcmp(buffer, "cont\n") == 0) + return 0; + if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") || + lua_pcall(L, 0, 0, 0)) { + fputs(lua_tostring(L, -1), stderr); + fputs("\n", stderr); + } + lua_settop(L, 0); /* remove eventual returns */ + } +} + + +#define LEVELS1 12 /* size of the first part of the stack */ +#define LEVELS2 10 /* size of the second part of the stack */ + +static int db_errorfb (lua_State *L) { + int level; + int firstpart = 1; /* still before eventual `...' */ + int arg; + lua_State *L1 = getthread(L, &arg); + lua_Debug ar; + if (lua_isnumber(L, arg+2)) { + level = (int)lua_tointeger(L, arg+2); + lua_pop(L, 1); + } + else + level = (L == L1) ? 1 : 0; /* level 0 may be this own function */ + if (lua_gettop(L) == arg) + lua_pushliteral(L, ""); + else if (!lua_isstring(L, arg+1)) return 1; /* message is not a string */ + else lua_pushliteral(L, "\n"); + lua_pushliteral(L, "stack traceback:"); + while (lua_getstack(L1, level++, &ar)) { + if (level > LEVELS1 && firstpart) { + /* no more than `LEVELS2' more levels? */ + if (!lua_getstack(L1, level+LEVELS2, &ar)) + level--; /* keep going */ + else { + lua_pushliteral(L, "\n\t..."); /* too many levels */ + while (lua_getstack(L1, level+LEVELS2, &ar)) /* find last levels */ + level++; + } + firstpart = 0; + continue; + } + lua_pushliteral(L, "\n\t"); + lua_getinfo(L1, "Snl", &ar); + lua_pushfstring(L, "%s:", ar.short_src); + if (ar.currentline > 0) + lua_pushfstring(L, "%d:", ar.currentline); + if (*ar.namewhat != '\0') /* is there a name? */ + lua_pushfstring(L, " in function " LUA_QS, ar.name); + else { + if (*ar.what == 'm') /* main? */ + lua_pushfstring(L, " in main chunk"); + else if (*ar.what == 'C' || *ar.what == 't') + lua_pushliteral(L, " ?"); /* C function or tail call */ + else + lua_pushfstring(L, " in function <%s:%d>", + ar.short_src, ar.linedefined); + } + lua_concat(L, lua_gettop(L) - arg); + } + lua_concat(L, lua_gettop(L) - arg); + return 1; +} + + +static const luaL_Reg dblib[] = { + {"debug", db_debug}, + {"getfenv", db_getfenv}, + {"gethook", db_gethook}, + {"getinfo", db_getinfo}, + {"getlocal", db_getlocal}, + {"getregistry", db_getregistry}, + {"getmetatable", db_getmetatable}, + {"getupvalue", db_getupvalue}, + {"setfenv", db_setfenv}, + {"sethook", db_sethook}, + {"setlocal", db_setlocal}, + {"setmetatable", db_setmetatable}, + {"setupvalue", db_setupvalue}, + {"traceback", db_errorfb}, + {NULL, NULL} +}; + + +LUALIB_API int luaopen_debug (lua_State *L) { + luaL_register(L, LUA_DBLIBNAME, dblib); + return 1; +} + diff --git a/engines/sword25/util/lua/ldebug.cpp b/engines/sword25/util/lua/ldebug.cpp new file mode 100644 index 0000000000..0b26522b31 --- /dev/null +++ b/engines/sword25/util/lua/ldebug.cpp @@ -0,0 +1,622 @@ +/* +** $Id$ +** Debug Interface +** See Copyright Notice in lua.h +*/ + + +#include <stdarg.h> +#include <stddef.h> +#include <string.h> + + +#define ldebug_c +#define LUA_CORE + +#include "lua.h" + +#include "lapi.h" +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + + +static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name); + + +static int currentpc (lua_State *L, CallInfo *ci) { + if (!isLua(ci)) return -1; /* function is not a Lua function? */ + if (ci == L->ci) + ci->savedpc = L->savedpc; + return pcRel(ci->savedpc, ci_func(ci)->l.p); +} + + +static int currentline (lua_State *L, CallInfo *ci) { + int pc = currentpc(L, ci); + if (pc < 0) + return -1; /* only active lua functions have current-line information */ + else + return getline(ci_func(ci)->l.p, pc); +} + + +/* +** this function can be called asynchronous (e.g. during a signal) +*/ +LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { + if (func == NULL || mask == 0) { /* turn off hooks? */ + mask = 0; + func = NULL; + } + L->hook = func; + L->basehookcount = count; + resethookcount(L); + L->hookmask = cast_byte(mask); + return 1; +} + + +LUA_API lua_Hook lua_gethook (lua_State *L) { + return L->hook; +} + + +LUA_API int lua_gethookmask (lua_State *L) { + return L->hookmask; +} + + +LUA_API int lua_gethookcount (lua_State *L) { + return L->basehookcount; +} + + +LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) { + int status; + CallInfo *ci; + lua_lock(L); + for (ci = L->ci; level > 0 && ci > L->base_ci; ci--) { + level--; + if (f_isLua(ci)) /* Lua function? */ + level -= ci->tailcalls; /* skip lost tail calls */ + } + if (level == 0 && ci > L->base_ci) { /* level found? */ + status = 1; + ar->i_ci = cast_int(ci - L->base_ci); + } + else if (level < 0) { /* level is of a lost tail call? */ + status = 1; + ar->i_ci = 0; + } + else status = 0; /* no such level */ + lua_unlock(L); + return status; +} + + +static Proto *getluaproto (CallInfo *ci) { + return (isLua(ci) ? ci_func(ci)->l.p : NULL); +} + + +static const char *findlocal (lua_State *L, CallInfo *ci, int n) { + const char *name; + Proto *fp = getluaproto(ci); + if (fp && (name = luaF_getlocalname(fp, n, currentpc(L, ci))) != NULL) + return name; /* is a local variable in a Lua function */ + else { + StkId limit = (ci == L->ci) ? L->top : (ci+1)->func; + if (limit - ci->base >= n && n > 0) /* is 'n' inside 'ci' stack? */ + return "(*temporary)"; + else + return NULL; + } +} + + +LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { + CallInfo *ci = L->base_ci + ar->i_ci; + const char *name = findlocal(L, ci, n); + lua_lock(L); + if (name) + luaA_pushobject(L, ci->base + (n - 1)); + lua_unlock(L); + return name; +} + + +LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { + CallInfo *ci = L->base_ci + ar->i_ci; + const char *name = findlocal(L, ci, n); + lua_lock(L); + if (name) + setobjs2s(L, ci->base + (n - 1), L->top - 1); + L->top--; /* pop value */ + lua_unlock(L); + return name; +} + + +static void funcinfo (lua_Debug *ar, Closure *cl) { + if (cl->c.isC) { + ar->source = "=[C]"; + ar->linedefined = -1; + ar->lastlinedefined = -1; + ar->what = "C"; + } + else { + ar->source = getstr(cl->l.p->source); + ar->linedefined = cl->l.p->linedefined; + ar->lastlinedefined = cl->l.p->lastlinedefined; + ar->what = (ar->linedefined == 0) ? "main" : "Lua"; + } + luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE); +} + + +static void info_tailcall (lua_Debug *ar) { + ar->name = ar->namewhat = ""; + ar->what = "tail"; + ar->lastlinedefined = ar->linedefined = ar->currentline = -1; + ar->source = "=(tail call)"; + luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE); + ar->nups = 0; +} + + +static void collectvalidlines (lua_State *L, Closure *f) { + if (f == NULL || f->c.isC) { + setnilvalue(L->top); + } + else { + Table *t = luaH_new(L, 0, 0); + int *lineinfo = f->l.p->lineinfo; + int i; + for (i=0; i<f->l.p->sizelineinfo; i++) + setbvalue(luaH_setnum(L, t, lineinfo[i]), 1); + sethvalue(L, L->top, t); + } + incr_top(L); +} + + +static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, + Closure *f, CallInfo *ci) { + int status = 1; + if (f == NULL) { + info_tailcall(ar); + return status; + } + for (; *what; what++) { + switch (*what) { + case 'S': { + funcinfo(ar, f); + break; + } + case 'l': { + ar->currentline = (ci) ? currentline(L, ci) : -1; + break; + } + case 'u': { + ar->nups = f->c.nupvalues; + break; + } + case 'n': { + ar->namewhat = (ci) ? getfuncname(L, ci, &ar->name) : NULL; + if (ar->namewhat == NULL) { + ar->namewhat = ""; /* not found */ + ar->name = NULL; + } + break; + } + case 'L': + case 'f': /* handled by lua_getinfo */ + break; + default: status = 0; /* invalid option */ + } + } + return status; +} + + +LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { + int status; + Closure *f = NULL; + CallInfo *ci = NULL; + lua_lock(L); + if (*what == '>') { + StkId func = L->top - 1; + luai_apicheck(L, ttisfunction(func)); + what++; /* skip the '>' */ + f = clvalue(func); + L->top--; /* pop function */ + } + else if (ar->i_ci != 0) { /* no tail call? */ + ci = L->base_ci + ar->i_ci; + lua_assert(ttisfunction(ci->func)); + f = clvalue(ci->func); + } + status = auxgetinfo(L, what, ar, f, ci); + if (strchr(what, 'f')) { + if (f == NULL) setnilvalue(L->top); + else setclvalue(L, L->top, f); + incr_top(L); + } + if (strchr(what, 'L')) + collectvalidlines(L, f); + lua_unlock(L); + return status; +} + + +/* +** {====================================================== +** Symbolic Execution and code checker +** ======================================================= +*/ + +#define check(x) if (!(x)) return 0; + +#define checkjump(pt,pc) check(0 <= pc && pc < pt->sizecode) + +#define checkreg(pt,reg) check((reg) < (pt)->maxstacksize) + + + +static int precheck (const Proto *pt) { + check(pt->maxstacksize <= MAXSTACK); + lua_assert(pt->numparams+(pt->is_vararg & VARARG_HASARG) <= pt->maxstacksize); + lua_assert(!(pt->is_vararg & VARARG_NEEDSARG) || + (pt->is_vararg & VARARG_HASARG)); + check(pt->sizeupvalues <= pt->nups); + check(pt->sizelineinfo == pt->sizecode || pt->sizelineinfo == 0); + check(GET_OPCODE(pt->code[pt->sizecode-1]) == OP_RETURN); + return 1; +} + + +#define checkopenop(pt,pc) luaG_checkopenop((pt)->code[(pc)+1]) + +int luaG_checkopenop (Instruction i) { + switch (GET_OPCODE(i)) { + case OP_CALL: + case OP_TAILCALL: + case OP_RETURN: + case OP_SETLIST: { + check(GETARG_B(i) == 0); + return 1; + } + default: return 0; /* invalid instruction after an open call */ + } +} + + +static int checkArgMode (const Proto *pt, int r, enum OpArgMask mode) { + switch (mode) { + case OpArgN: check(r == 0); break; + case OpArgU: break; + case OpArgR: checkreg(pt, r); break; + case OpArgK: + check(ISK(r) ? INDEXK(r) < pt->sizek : r < pt->maxstacksize); + break; + } + return 1; +} + + +static Instruction symbexec (const Proto *pt, int lastpc, int reg) { + int pc; + int last; /* stores position of last instruction that changed `reg' */ + last = pt->sizecode-1; /* points to final return (a `neutral' instruction) */ + check(precheck(pt)); + for (pc = 0; pc < lastpc; pc++) { + Instruction i = pt->code[pc]; + OpCode op = GET_OPCODE(i); + int a = GETARG_A(i); + int b = 0; + int c = 0; + check(op < NUM_OPCODES); + checkreg(pt, a); + switch (getOpMode(op)) { + case iABC: { + b = GETARG_B(i); + c = GETARG_C(i); + check(checkArgMode(pt, b, getBMode(op))); + check(checkArgMode(pt, c, getCMode(op))); + break; + } + case iABx: { + b = GETARG_Bx(i); + if (getBMode(op) == OpArgK) check(b < pt->sizek); + break; + } + case iAsBx: { + b = GETARG_sBx(i); + if (getBMode(op) == OpArgR) { + int dest = pc+1+b; + check(0 <= dest && dest < pt->sizecode); + if (dest > 0) { + /* cannot jump to a setlist count */ + Instruction d = pt->code[dest-1]; + check(!(GET_OPCODE(d) == OP_SETLIST && GETARG_C(d) == 0)); + } + } + break; + } + } + if (testAMode(op)) { + if (a == reg) last = pc; /* change register `a' */ + } + if (testTMode(op)) { + check(pc+2 < pt->sizecode); /* check skip */ + check(GET_OPCODE(pt->code[pc+1]) == OP_JMP); + } + switch (op) { + case OP_LOADBOOL: { + check(c == 0 || pc+2 < pt->sizecode); /* check its jump */ + break; + } + case OP_LOADNIL: { + if (a <= reg && reg <= b) + last = pc; /* set registers from `a' to `b' */ + break; + } + case OP_GETUPVAL: + case OP_SETUPVAL: { + check(b < pt->nups); + break; + } + case OP_GETGLOBAL: + case OP_SETGLOBAL: { + check(ttisstring(&pt->k[b])); + break; + } + case OP_SELF: { + checkreg(pt, a+1); + if (reg == a+1) last = pc; + break; + } + case OP_CONCAT: { + check(b < c); /* at least two operands */ + break; + } + case OP_TFORLOOP: { + check(c >= 1); /* at least one result (control variable) */ + checkreg(pt, a+2+c); /* space for results */ + if (reg >= a+2) last = pc; /* affect all regs above its base */ + break; + } + case OP_FORLOOP: + case OP_FORPREP: + checkreg(pt, a+3); + /* go through */ + case OP_JMP: { + int dest = pc+1+b; + /* not full check and jump is forward and do not skip `lastpc'? */ + if (reg != NO_REG && pc < dest && dest <= lastpc) + pc += b; /* do the jump */ + break; + } + case OP_CALL: + case OP_TAILCALL: { + if (b != 0) { + checkreg(pt, a+b-1); + } + c--; /* c = num. returns */ + if (c == LUA_MULTRET) { + check(checkopenop(pt, pc)); + } + else if (c != 0) + checkreg(pt, a+c-1); + if (reg >= a) last = pc; /* affect all registers above base */ + break; + } + case OP_RETURN: { + b--; /* b = num. returns */ + if (b > 0) checkreg(pt, a+b-1); + break; + } + case OP_SETLIST: { + if (b > 0) checkreg(pt, a + b); + if (c == 0) pc++; + break; + } + case OP_CLOSURE: { + int nup, j; + check(b < pt->sizep); + nup = pt->p[b]->nups; + check(pc + nup < pt->sizecode); + for (j = 1; j <= nup; j++) { + OpCode op1 = GET_OPCODE(pt->code[pc + j]); + check(op1 == OP_GETUPVAL || op1 == OP_MOVE); + } + if (reg != NO_REG) /* tracing? */ + pc += nup; /* do not 'execute' these pseudo-instructions */ + break; + } + case OP_VARARG: { + check((pt->is_vararg & VARARG_ISVARARG) && + !(pt->is_vararg & VARARG_NEEDSARG)); + b--; + if (b == LUA_MULTRET) check(checkopenop(pt, pc)); + checkreg(pt, a+b-1); + break; + } + default: break; + } + } + return pt->code[last]; +} + +#undef check +#undef checkjump +#undef checkreg + +/* }====================================================== */ + + +int luaG_checkcode (const Proto *pt) { + return (symbexec(pt, pt->sizecode, NO_REG) != 0); +} + + +static const char *kname (Proto *p, int c) { + if (ISK(c) && ttisstring(&p->k[INDEXK(c)])) + return svalue(&p->k[INDEXK(c)]); + else + return "?"; +} + + +static const char *getobjname (lua_State *L, CallInfo *ci, int stackpos, + const char **name) { + if (isLua(ci)) { /* a Lua function? */ + Proto *p = ci_func(ci)->l.p; + int pc = currentpc(L, ci); + Instruction i; + *name = luaF_getlocalname(p, stackpos+1, pc); + if (*name) /* is a local? */ + return "local"; + i = symbexec(p, pc, stackpos); /* try symbolic execution */ + lua_assert(pc != -1); + switch (GET_OPCODE(i)) { + case OP_GETGLOBAL: { + int g = GETARG_Bx(i); /* global index */ + lua_assert(ttisstring(&p->k[g])); + *name = svalue(&p->k[g]); + return "global"; + } + case OP_MOVE: { + int a = GETARG_A(i); + int b = GETARG_B(i); /* move from `b' to `a' */ + if (b < a) + return getobjname(L, ci, b, name); /* get name for `b' */ + break; + } + case OP_GETTABLE: { + int k = GETARG_C(i); /* key index */ + *name = kname(p, k); + return "field"; + } + case OP_GETUPVAL: { + int u = GETARG_B(i); /* upvalue index */ + *name = p->upvalues ? getstr(p->upvalues[u]) : "?"; + return "upvalue"; + } + case OP_SELF: { + int k = GETARG_C(i); /* key index */ + *name = kname(p, k); + return "method"; + } + default: break; + } + } + return NULL; /* no useful name found */ +} + + +static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { + Instruction i; + if ((isLua(ci) && ci->tailcalls > 0) || !isLua(ci - 1)) + return NULL; /* calling function is not Lua (or is unknown) */ + ci--; /* calling function */ + i = ci_func(ci)->l.p->code[currentpc(L, ci)]; + if (GET_OPCODE(i) == OP_CALL || GET_OPCODE(i) == OP_TAILCALL || + GET_OPCODE(i) == OP_TFORLOOP) + return getobjname(L, ci, GETARG_A(i), name); + else + return NULL; /* no useful name can be found */ +} + + +/* only ANSI way to check whether a pointer points to an array */ +static int isinstack (CallInfo *ci, const TValue *o) { + StkId p; + for (p = ci->base; p < ci->top; p++) + if (o == p) return 1; + return 0; +} + + +void luaG_typeerror (lua_State *L, const TValue *o, const char *op) { + const char *name = NULL; + const char *t = luaT_typenames[ttype(o)]; + const char *kind = (isinstack(L->ci, o)) ? + getobjname(L, L->ci, cast_int(o - L->base), &name) : + NULL; + if (kind) + luaG_runerror(L, "attempt to %s %s " LUA_QS " (a %s value)", + op, kind, name, t); + else + luaG_runerror(L, "attempt to %s a %s value", op, t); +} + + +void luaG_concaterror (lua_State *L, StkId p1, StkId p2) { + if (ttisstring(p1) || ttisnumber(p1)) p1 = p2; + lua_assert(!ttisstring(p1) && !ttisnumber(p1)); + luaG_typeerror(L, p1, "concatenate"); +} + + +void luaG_aritherror (lua_State *L, const TValue *p1, const TValue *p2) { + TValue temp; + if (luaV_tonumber(p1, &temp) == NULL) + p2 = p1; /* first operand is wrong */ + luaG_typeerror(L, p2, "perform arithmetic on"); +} + + +int luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { + const char *t1 = luaT_typenames[ttype(p1)]; + const char *t2 = luaT_typenames[ttype(p2)]; + if (t1[2] == t2[2]) + luaG_runerror(L, "attempt to compare two %s values", t1); + else + luaG_runerror(L, "attempt to compare %s with %s", t1, t2); + return 0; +} + + +static void addinfo (lua_State *L, const char *msg) { + CallInfo *ci = L->ci; + if (isLua(ci)) { /* is Lua code? */ + char buff[LUA_IDSIZE]; /* add file:line information */ + int line = currentline(L, ci); + luaO_chunkid(buff, getstr(getluaproto(ci)->source), LUA_IDSIZE); + luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); + } +} + + +void luaG_errormsg (lua_State *L) { + if (L->errfunc != 0) { /* is there an error handling function? */ + StkId errfunc = restorestack(L, L->errfunc); + if (!ttisfunction(errfunc)) luaD_throw(L, LUA_ERRERR); + setobjs2s(L, L->top, L->top - 1); /* move argument */ + setobjs2s(L, L->top - 1, errfunc); /* push function */ + incr_top(L); + luaD_call(L, L->top - 2, 1); /* call it */ + } + luaD_throw(L, LUA_ERRRUN); +} + + +void luaG_runerror (lua_State *L, const char *fmt, ...) { + va_list argp; + va_start(argp, fmt); + addinfo(L, luaO_pushvfstring(L, fmt, argp)); + va_end(argp); + luaG_errormsg(L); +} + diff --git a/engines/sword25/util/lua/ldebug.h b/engines/sword25/util/lua/ldebug.h new file mode 100644 index 0000000000..22226b4096 --- /dev/null +++ b/engines/sword25/util/lua/ldebug.h @@ -0,0 +1,33 @@ +/* +** $Id$ +** Auxiliary functions from Debug Interface module +** See Copyright Notice in lua.h +*/ + +#ifndef ldebug_h +#define ldebug_h + + +#include "lstate.h" + + +#define pcRel(pc, p) (cast(int, (pc) - (p)->code) - 1) + +#define getline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : 0) + +#define resethookcount(L) (L->hookcount = L->basehookcount) + + +LUAI_FUNC void luaG_typeerror (lua_State *L, const TValue *o, + const char *opname); +LUAI_FUNC void luaG_concaterror (lua_State *L, StkId p1, StkId p2); +LUAI_FUNC void luaG_aritherror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC int luaG_ordererror (lua_State *L, const TValue *p1, + const TValue *p2); +LUAI_FUNC void luaG_runerror (lua_State *L, const char *fmt, ...); +LUAI_FUNC void luaG_errormsg (lua_State *L); +LUAI_FUNC int luaG_checkcode (const Proto *pt); +LUAI_FUNC int luaG_checkopenop (Instruction i); + +#endif diff --git a/engines/sword25/util/lua/ldo.cpp b/engines/sword25/util/lua/ldo.cpp new file mode 100644 index 0000000000..07508fbb14 --- /dev/null +++ b/engines/sword25/util/lua/ldo.cpp @@ -0,0 +1,518 @@ +/* +** $Id$ +** Stack and Call structure of Lua +** See Copyright Notice in lua.h +*/ + + +#include <setjmp.h> +#include <stdlib.h> +#include <string.h> + +#define ldo_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lundump.h" +#include "lvm.h" +#include "lzio.h" + + + + +/* +** {====================================================== +** Error-recovery functions +** ======================================================= +*/ + + +/* chain list of long jump buffers */ +struct lua_longjmp { + struct lua_longjmp *previous; + luai_jmpbuf b; + volatile int status; /* error code */ +}; + + +void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { + switch (errcode) { + case LUA_ERRMEM: { + setsvalue2s(L, oldtop, luaS_newliteral(L, MEMERRMSG)); + break; + } + case LUA_ERRERR: { + setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); + break; + } + case LUA_ERRSYNTAX: + case LUA_ERRRUN: { + setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ + break; + } + } + L->top = oldtop + 1; +} + + +static void restore_stack_limit (lua_State *L) { + lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); + if (L->size_ci > LUAI_MAXCALLS) { /* there was an overflow? */ + int inuse = cast_int(L->ci - L->base_ci); + if (inuse + 1 < LUAI_MAXCALLS) /* can `undo' overflow? */ + luaD_reallocCI(L, LUAI_MAXCALLS); + } +} + + +static void resetstack (lua_State *L, int status) { + L->ci = L->base_ci; + L->base = L->ci->base; + luaF_close(L, L->base); /* close eventual pending closures */ + luaD_seterrorobj(L, status, L->base); + L->nCcalls = L->baseCcalls; + L->allowhook = 1; + restore_stack_limit(L); + L->errfunc = 0; + L->errorJmp = NULL; +} + + +void luaD_throw (lua_State *L, int errcode) { + if (L->errorJmp) { + L->errorJmp->status = errcode; + LUAI_THROW(L, L->errorJmp); + } + else { + L->status = cast_byte(errcode); + if (G(L)->panic) { + resetstack(L, errcode); + lua_unlock(L); + G(L)->panic(L); + } + exit(EXIT_FAILURE); + } +} + + +int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { + struct lua_longjmp lj; + lj.status = 0; + lj.previous = L->errorJmp; /* chain new error handler */ + L->errorJmp = &lj; + LUAI_TRY(L, &lj, + (*f)(L, ud); + ); + L->errorJmp = lj.previous; /* restore old error handler */ + return lj.status; +} + +/* }====================================================== */ + + +static void correctstack (lua_State *L, TValue *oldstack) { + CallInfo *ci; + GCObject *up; + L->top = (L->top - oldstack) + L->stack; + for (up = L->openupval; up != NULL; up = up->gch.next) + gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack; + for (ci = L->base_ci; ci <= L->ci; ci++) { + ci->top = (ci->top - oldstack) + L->stack; + ci->base = (ci->base - oldstack) + L->stack; + ci->func = (ci->func - oldstack) + L->stack; + } + L->base = (L->base - oldstack) + L->stack; +} + + +void luaD_reallocstack (lua_State *L, int newsize) { + TValue *oldstack = L->stack; + int realsize = newsize + 1 + EXTRA_STACK; + lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); + luaM_reallocvector(L, L->stack, L->stacksize, realsize, TValue); + L->stacksize = realsize; + L->stack_last = L->stack+newsize; + correctstack(L, oldstack); +} + + +void luaD_reallocCI (lua_State *L, int newsize) { + CallInfo *oldci = L->base_ci; + luaM_reallocvector(L, L->base_ci, L->size_ci, newsize, CallInfo); + L->size_ci = newsize; + L->ci = (L->ci - oldci) + L->base_ci; + L->end_ci = L->base_ci + L->size_ci - 1; +} + + +void luaD_growstack (lua_State *L, int n) { + if (n <= L->stacksize) /* double size is enough? */ + luaD_reallocstack(L, 2*L->stacksize); + else + luaD_reallocstack(L, L->stacksize + n); +} + + +static CallInfo *growCI (lua_State *L) { + if (L->size_ci > LUAI_MAXCALLS) /* overflow while handling overflow? */ + luaD_throw(L, LUA_ERRERR); + else { + luaD_reallocCI(L, 2*L->size_ci); + if (L->size_ci > LUAI_MAXCALLS) + luaG_runerror(L, "stack overflow"); + } + return ++L->ci; +} + + +void luaD_callhook (lua_State *L, int event, int line) { + lua_Hook hook = L->hook; + if (hook && L->allowhook) { + ptrdiff_t top = savestack(L, L->top); + ptrdiff_t ci_top = savestack(L, L->ci->top); + lua_Debug ar; + ar.event = event; + ar.currentline = line; + if (event == LUA_HOOKTAILRET) + ar.i_ci = 0; /* tail call; no debug information about it */ + else + ar.i_ci = cast_int(L->ci - L->base_ci); + luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ + L->ci->top = L->top + LUA_MINSTACK; + lua_assert(L->ci->top <= L->stack_last); + L->allowhook = 0; /* cannot call hooks inside a hook */ + lua_unlock(L); + (*hook)(L, &ar); + lua_lock(L); + lua_assert(!L->allowhook); + L->allowhook = 1; + L->ci->top = restorestack(L, ci_top); + L->top = restorestack(L, top); + } +} + + +static StkId adjust_varargs (lua_State *L, Proto *p, int actual) { + int i; + int nfixargs = p->numparams; + Table *htab = NULL; + StkId base, fixed; + for (; actual < nfixargs; ++actual) + setnilvalue(L->top++); +#if defined(LUA_COMPAT_VARARG) + if (p->is_vararg & VARARG_NEEDSARG) { /* compat. with old-style vararg? */ + int nvar = actual - nfixargs; /* number of extra arguments */ + lua_assert(p->is_vararg & VARARG_HASARG); + luaC_checkGC(L); + htab = luaH_new(L, nvar, 1); /* create `arg' table */ + for (i=0; i<nvar; i++) /* put extra arguments into `arg' table */ + setobj2n(L, luaH_setnum(L, htab, i+1), L->top - nvar + i); + /* store counter in field `n' */ + setnvalue(luaH_setstr(L, htab, luaS_newliteral(L, "n")), cast_num(nvar)); + } +#endif + /* move fixed parameters to final position */ + fixed = L->top - actual; /* first fixed argument */ + base = L->top; /* final position of first argument */ + for (i=0; i<nfixargs; i++) { + setobjs2s(L, L->top++, fixed+i); + setnilvalue(fixed+i); + } + /* add `arg' parameter */ + if (htab) { + sethvalue(L, L->top++, htab); + lua_assert(iswhite(obj2gco(htab))); + } + return base; +} + + +static StkId tryfuncTM (lua_State *L, StkId func) { + const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL); + StkId p; + ptrdiff_t funcr = savestack(L, func); + if (!ttisfunction(tm)) + luaG_typeerror(L, func, "call"); + /* Open a hole inside the stack at `func' */ + for (p = L->top; p > func; p--) setobjs2s(L, p, p-1); + incr_top(L); + func = restorestack(L, funcr); /* previous call may change stack */ + setobj2s(L, func, tm); /* tag method is the new function to be called */ + return func; +} + + + +#define inc_ci(L) \ + ((L->ci == L->end_ci) ? growCI(L) : \ + (condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci)) + + +int luaD_precall (lua_State *L, StkId func, int nresults) { + LClosure *cl; + ptrdiff_t funcr; + if (!ttisfunction(func)) /* `func' is not a function? */ + func = tryfuncTM(L, func); /* check the `function' tag method */ + funcr = savestack(L, func); + cl = &clvalue(func)->l; + L->ci->savedpc = L->savedpc; + if (!cl->isC) { /* Lua function? prepare its call */ + CallInfo *ci; + StkId st, base; + Proto *p = cl->p; + luaD_checkstack(L, p->maxstacksize); + func = restorestack(L, funcr); + if (!p->is_vararg) { /* no varargs? */ + base = func + 1; + if (L->top > base + p->numparams) + L->top = base + p->numparams; + } + else { /* vararg function */ + int nargs = cast_int(L->top - func) - 1; + base = adjust_varargs(L, p, nargs); + func = restorestack(L, funcr); /* previous call may change the stack */ + } + ci = inc_ci(L); /* now `enter' new function */ + ci->func = func; + L->base = ci->base = base; + ci->top = L->base + p->maxstacksize; + lua_assert(ci->top <= L->stack_last); + L->savedpc = p->code; /* starting point */ + ci->tailcalls = 0; + ci->nresults = nresults; + for (st = L->top; st < ci->top; st++) + setnilvalue(st); + L->top = ci->top; + if (L->hookmask & LUA_MASKCALL) { + L->savedpc++; /* hooks assume 'pc' is already incremented */ + luaD_callhook(L, LUA_HOOKCALL, -1); + L->savedpc--; /* correct 'pc' */ + } + return PCRLUA; + } + else { /* if is a C function, call it */ + CallInfo *ci; + int n; + luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ + ci = inc_ci(L); /* now `enter' new function */ + ci->func = restorestack(L, funcr); + L->base = ci->base = ci->func + 1; + ci->top = L->top + LUA_MINSTACK; + lua_assert(ci->top <= L->stack_last); + ci->nresults = nresults; + if (L->hookmask & LUA_MASKCALL) + luaD_callhook(L, LUA_HOOKCALL, -1); + lua_unlock(L); + n = (*curr_func(L)->c.f)(L); /* do the actual call */ + lua_lock(L); + if (n < 0) /* yielding? */ + return PCRYIELD; + else { + luaD_poscall(L, L->top - n); + return PCRC; + } + } +} + + +static StkId callrethooks (lua_State *L, StkId firstResult) { + ptrdiff_t fr = savestack(L, firstResult); /* next call may change stack */ + luaD_callhook(L, LUA_HOOKRET, -1); + if (f_isLua(L->ci)) { /* Lua function? */ + while ((L->hookmask & LUA_MASKRET) && L->ci->tailcalls--) /* tail calls */ + luaD_callhook(L, LUA_HOOKTAILRET, -1); + } + return restorestack(L, fr); +} + + +int luaD_poscall (lua_State *L, StkId firstResult) { + StkId res; + int wanted, i; + CallInfo *ci; + if (L->hookmask & LUA_MASKRET) + firstResult = callrethooks(L, firstResult); + ci = L->ci--; + res = ci->func; /* res == final position of 1st result */ + wanted = ci->nresults; + L->base = (ci - 1)->base; /* restore base */ + L->savedpc = (ci - 1)->savedpc; /* restore savedpc */ + /* move results to correct place */ + for (i = wanted; i != 0 && firstResult < L->top; i--) + setobjs2s(L, res++, firstResult++); + while (i-- > 0) + setnilvalue(res++); + L->top = res; + return (wanted - LUA_MULTRET); /* 0 iff wanted == LUA_MULTRET */ +} + + +/* +** Call a function (C or Lua). The function to be called is at *func. +** The arguments are on the stack, right after the function. +** When returns, all the results are on the stack, starting at the original +** function position. +*/ +void luaD_call (lua_State *L, StkId func, int nResults) { + if (++L->nCcalls >= LUAI_MAXCCALLS) { + if (L->nCcalls == LUAI_MAXCCALLS) + luaG_runerror(L, "C stack overflow"); + else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) + luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ + } + if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */ + luaV_execute(L, 1); /* call it */ + L->nCcalls--; + luaC_checkGC(L); +} + + +static void resume (lua_State *L, void *ud) { + StkId firstArg = cast(StkId, ud); + CallInfo *ci = L->ci; + if (L->status == 0) { /* start coroutine? */ + lua_assert(ci == L->base_ci && firstArg > L->base); + if (luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA) + return; + } + else { /* resuming from previous yield */ + lua_assert(L->status == LUA_YIELD); + L->status = 0; + if (!f_isLua(ci)) { /* `common' yield? */ + /* finish interrupted execution of `OP_CALL' */ + lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL || + GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL); + if (luaD_poscall(L, firstArg)) /* complete it... */ + L->top = L->ci->top; /* and correct top if not multiple results */ + } + else /* yielded inside a hook: just continue its execution */ + L->base = L->ci->base; + } + luaV_execute(L, cast_int(L->ci - L->base_ci)); +} + + +static int resume_error (lua_State *L, const char *msg) { + L->top = L->ci->base; + setsvalue2s(L, L->top, luaS_new(L, msg)); + incr_top(L); + lua_unlock(L); + return LUA_ERRRUN; +} + + +LUA_API int lua_resume (lua_State *L, int nargs) { + int status; + lua_lock(L); + if (L->status != LUA_YIELD && (L->status != 0 || L->ci != L->base_ci)) + return resume_error(L, "cannot resume non-suspended coroutine"); + if (L->nCcalls >= LUAI_MAXCCALLS) + return resume_error(L, "C stack overflow"); + luai_userstateresume(L, nargs); + lua_assert(L->errfunc == 0); + L->baseCcalls = ++L->nCcalls; + status = luaD_rawrunprotected(L, resume, L->top - nargs); + if (status != 0) { /* error? */ + L->status = cast_byte(status); /* mark thread as `dead' */ + luaD_seterrorobj(L, status, L->top); + L->ci->top = L->top; + } + else { + lua_assert(L->nCcalls == L->baseCcalls); + status = L->status; + } + --L->nCcalls; + lua_unlock(L); + return status; +} + + +LUA_API int lua_yield (lua_State *L, int nresults) { + luai_userstateyield(L, nresults); + lua_lock(L); + if (L->nCcalls > L->baseCcalls) + luaG_runerror(L, "attempt to yield across metamethod/C-call boundary"); + L->base = L->top - nresults; /* protect stack slots below */ + L->status = LUA_YIELD; + lua_unlock(L); + return -1; +} + + +int luaD_pcall (lua_State *L, Pfunc func, void *u, + ptrdiff_t old_top, ptrdiff_t ef) { + int status; + unsigned short oldnCcalls = L->nCcalls; + ptrdiff_t old_ci = saveci(L, L->ci); + lu_byte old_allowhooks = L->allowhook; + ptrdiff_t old_errfunc = L->errfunc; + L->errfunc = ef; + status = luaD_rawrunprotected(L, func, u); + if (status != 0) { /* an error occurred? */ + StkId oldtop = restorestack(L, old_top); + luaF_close(L, oldtop); /* close eventual pending closures */ + luaD_seterrorobj(L, status, oldtop); + L->nCcalls = oldnCcalls; + L->ci = restoreci(L, old_ci); + L->base = L->ci->base; + L->savedpc = L->ci->savedpc; + L->allowhook = old_allowhooks; + restore_stack_limit(L); + } + L->errfunc = old_errfunc; + return status; +} + + + +/* +** Execute a protected parser. +*/ +struct SParser { /* data to `f_parser' */ + ZIO *z; + Mbuffer buff; /* buffer to be used by the scanner */ + const char *name; +}; + +static void f_parser (lua_State *L, void *ud) { + int i; + Proto *tf; + Closure *cl; + struct SParser *p = cast(struct SParser *, ud); + int c = luaZ_lookahead(p->z); + luaC_checkGC(L); + tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z, + &p->buff, p->name); + cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L))); + cl->l.p = tf; + for (i = 0; i < tf->nups; i++) /* initialize eventual upvalues */ + cl->l.upvals[i] = luaF_newupval(L); + setclvalue(L, L->top, cl); + incr_top(L); +} + + +int luaD_protectedparser (lua_State *L, ZIO *z, const char *name) { + struct SParser p; + int status; + p.z = z; p.name = name; + luaZ_initbuffer(L, &p.buff); + status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc); + luaZ_freebuffer(L, &p.buff); + return status; +} + + diff --git a/engines/sword25/util/lua/ldo.h b/engines/sword25/util/lua/ldo.h new file mode 100644 index 0000000000..4c97134805 --- /dev/null +++ b/engines/sword25/util/lua/ldo.h @@ -0,0 +1,57 @@ +/* +** $Id$ +** Stack and Call structure of Lua +** See Copyright Notice in lua.h +*/ + +#ifndef ldo_h +#define ldo_h + + +#include "lobject.h" +#include "lstate.h" +#include "lzio.h" + + +#define luaD_checkstack(L,n) \ + if ((char *)L->stack_last - (char *)L->top <= (n)*(int)sizeof(TValue)) \ + luaD_growstack(L, n); \ + else condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); + + +#define incr_top(L) {luaD_checkstack(L,1); L->top++;} + +#define savestack(L,p) ((char *)(p) - (char *)L->stack) +#define restorestack(L,n) ((TValue *)((char *)L->stack + (n))) + +#define saveci(L,p) ((char *)(p) - (char *)L->base_ci) +#define restoreci(L,n) ((CallInfo *)((char *)L->base_ci + (n))) + + +/* results from luaD_precall */ +#define PCRLUA 0 /* initiated a call to a Lua function */ +#define PCRC 1 /* did a call to a C function */ +#define PCRYIELD 2 /* C funtion yielded */ + + +/* type of protected functions, to be ran by `runprotected' */ +typedef void (*Pfunc) (lua_State *L, void *ud); + +LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name); +LUAI_FUNC void luaD_callhook (lua_State *L, int event, int line); +LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults); +LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); +LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, + ptrdiff_t oldtop, ptrdiff_t ef); +LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult); +LUAI_FUNC void luaD_reallocCI (lua_State *L, int newsize); +LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize); +LUAI_FUNC void luaD_growstack (lua_State *L, int n); + +LUAI_FUNC void luaD_throw (lua_State *L, int errcode); +LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); + +LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop); + +#endif + diff --git a/engines/sword25/util/lua/ldump.cpp b/engines/sword25/util/lua/ldump.cpp new file mode 100644 index 0000000000..3ce16542d6 --- /dev/null +++ b/engines/sword25/util/lua/ldump.cpp @@ -0,0 +1,164 @@ +/* +** $Id$ +** save precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#include <stddef.h> + +#define ldump_c +#define LUA_CORE + +#include "lua.h" + +#include "lobject.h" +#include "lstate.h" +#include "lundump.h" + +typedef struct { + lua_State* L; + lua_Writer writer; + void* data; + int strip; + int status; +} DumpState; + +#define DumpMem(b,n,size,D) DumpBlock(b,(n)*(size),D) +#define DumpVar(x,D) DumpMem(&x,1,sizeof(x),D) + +static void DumpBlock(const void* b, size_t size, DumpState* D) +{ + if (D->status==0) + { + lua_unlock(D->L); + D->status=(*D->writer)(D->L,b,size,D->data); + lua_lock(D->L); + } +} + +static void DumpChar(int y, DumpState* D) +{ + char x=(char)y; + DumpVar(x,D); +} + +static void DumpInt(int x, DumpState* D) +{ + DumpVar(x,D); +} + +static void DumpNumber(lua_Number x, DumpState* D) +{ + DumpVar(x,D); +} + +static void DumpVector(const void* b, int n, size_t size, DumpState* D) +{ + DumpInt(n,D); + DumpMem(b,n,size,D); +} + +static void DumpString(const TString* s, DumpState* D) +{ + if (s==NULL || getstr(s)==NULL) + { + size_t size=0; + DumpVar(size,D); + } + else + { + size_t size=s->tsv.len+1; /* include trailing '\0' */ + DumpVar(size,D); + DumpBlock(getstr(s),size,D); + } +} + +#define DumpCode(f,D) DumpVector(f->code,f->sizecode,sizeof(Instruction),D) + +static void DumpFunction(const Proto* f, const TString* p, DumpState* D); + +static void DumpConstants(const Proto* f, DumpState* D) +{ + int i,n=f->sizek; + DumpInt(n,D); + for (i=0; i<n; i++) + { + const TValue* o=&f->k[i]; + DumpChar(ttype(o),D); + switch (ttype(o)) + { + case LUA_TNIL: + break; + case LUA_TBOOLEAN: + DumpChar(bvalue(o),D); + break; + case LUA_TNUMBER: + DumpNumber(nvalue(o),D); + break; + case LUA_TSTRING: + DumpString(rawtsvalue(o),D); + break; + default: + lua_assert(0); /* cannot happen */ + break; + } + } + n=f->sizep; + DumpInt(n,D); + for (i=0; i<n; i++) DumpFunction(f->p[i],f->source,D); +} + +static void DumpDebug(const Proto* f, DumpState* D) +{ + int i,n; + n= (D->strip) ? 0 : f->sizelineinfo; + DumpVector(f->lineinfo,n,sizeof(int),D); + n= (D->strip) ? 0 : f->sizelocvars; + DumpInt(n,D); + for (i=0; i<n; i++) + { + DumpString(f->locvars[i].varname,D); + DumpInt(f->locvars[i].startpc,D); + DumpInt(f->locvars[i].endpc,D); + } + n= (D->strip) ? 0 : f->sizeupvalues; + DumpInt(n,D); + for (i=0; i<n; i++) DumpString(f->upvalues[i],D); +} + +static void DumpFunction(const Proto* f, const TString* p, DumpState* D) +{ + DumpString((f->source==p || D->strip) ? NULL : f->source,D); + DumpInt(f->linedefined,D); + DumpInt(f->lastlinedefined,D); + DumpChar(f->nups,D); + DumpChar(f->numparams,D); + DumpChar(f->is_vararg,D); + DumpChar(f->maxstacksize,D); + DumpCode(f,D); + DumpConstants(f,D); + DumpDebug(f,D); +} + +static void DumpHeader(DumpState* D) +{ + char h[LUAC_HEADERSIZE]; + luaU_header(h); + DumpBlock(h,LUAC_HEADERSIZE,D); +} + +/* +** dump Lua function as precompiled chunk +*/ +int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip) +{ + DumpState D; + D.L=L; + D.writer=w; + D.data=data; + D.strip=strip; + D.status=0; + DumpHeader(&D); + DumpFunction(f,NULL,&D); + return D.status; +} diff --git a/engines/sword25/util/lua/lfunc.cpp b/engines/sword25/util/lua/lfunc.cpp new file mode 100644 index 0000000000..ce7acf4e77 --- /dev/null +++ b/engines/sword25/util/lua/lfunc.cpp @@ -0,0 +1,174 @@ +/* +** $Id$ +** Auxiliary functions to manipulate prototypes and closures +** See Copyright Notice in lua.h +*/ + + +#include <stddef.h> + +#define lfunc_c +#define LUA_CORE + +#include "lua.h" + +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" + + + +Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e) { + Closure *c = cast(Closure *, luaM_malloc(L, sizeCclosure(nelems))); + luaC_link(L, obj2gco(c), LUA_TFUNCTION); + c->c.isC = 1; + c->c.env = e; + c->c.nupvalues = cast_byte(nelems); + return c; +} + + +Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e) { + Closure *c = cast(Closure *, luaM_malloc(L, sizeLclosure(nelems))); + luaC_link(L, obj2gco(c), LUA_TFUNCTION); + c->l.isC = 0; + c->l.env = e; + c->l.nupvalues = cast_byte(nelems); + while (nelems--) c->l.upvals[nelems] = NULL; + return c; +} + + +UpVal *luaF_newupval (lua_State *L) { + UpVal *uv = luaM_new(L, UpVal); + luaC_link(L, obj2gco(uv), LUA_TUPVAL); + uv->v = &uv->u.value; + setnilvalue(uv->v); + return uv; +} + + +UpVal *luaF_findupval (lua_State *L, StkId level) { + global_State *g = G(L); + GCObject **pp = &L->openupval; + UpVal *p; + UpVal *uv; + while (*pp != NULL && (p = ngcotouv(*pp))->v >= level) { + lua_assert(p->v != &p->u.value); + if (p->v == level) { /* found a corresponding upvalue? */ + if (isdead(g, obj2gco(p))) /* is it dead? */ + changewhite(obj2gco(p)); /* ressurect it */ + return p; + } + pp = &p->next; + } + uv = luaM_new(L, UpVal); /* not found: create a new one */ + uv->tt = LUA_TUPVAL; + uv->marked = luaC_white(g); + uv->v = level; /* current value lives in the stack */ + uv->next = *pp; /* chain it in the proper position */ + *pp = obj2gco(uv); + uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */ + uv->u.l.next = g->uvhead.u.l.next; + uv->u.l.next->u.l.prev = uv; + g->uvhead.u.l.next = uv; + lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + return uv; +} + + +static void unlinkupval (UpVal *uv) { + lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + uv->u.l.next->u.l.prev = uv->u.l.prev; /* remove from `uvhead' list */ + uv->u.l.prev->u.l.next = uv->u.l.next; +} + + +void luaF_freeupval (lua_State *L, UpVal *uv) { + if (uv->v != &uv->u.value) /* is it open? */ + unlinkupval(uv); /* remove from open list */ + luaM_free(L, uv); /* free upvalue */ +} + + +void luaF_close (lua_State *L, StkId level) { + UpVal *uv; + global_State *g = G(L); + while (L->openupval != NULL && (uv = ngcotouv(L->openupval))->v >= level) { + GCObject *o = obj2gco(uv); + lua_assert(!isblack(o) && uv->v != &uv->u.value); + L->openupval = uv->next; /* remove from `open' list */ + if (isdead(g, o)) + luaF_freeupval(L, uv); /* free upvalue */ + else { + unlinkupval(uv); + setobj(L, &uv->u.value, uv->v); + uv->v = &uv->u.value; /* now current value lives here */ + luaC_linkupval(L, uv); /* link upvalue into `gcroot' list */ + } + } +} + + +Proto *luaF_newproto (lua_State *L) { + Proto *f = luaM_new(L, Proto); + luaC_link(L, obj2gco(f), LUA_TPROTO); + f->k = NULL; + f->sizek = 0; + f->p = NULL; + f->sizep = 0; + f->code = NULL; + f->sizecode = 0; + f->sizelineinfo = 0; + f->sizeupvalues = 0; + f->nups = 0; + f->upvalues = NULL; + f->numparams = 0; + f->is_vararg = 0; + f->maxstacksize = 0; + f->lineinfo = NULL; + f->sizelocvars = 0; + f->locvars = NULL; + f->linedefined = 0; + f->lastlinedefined = 0; + f->source = NULL; + return f; +} + + +void luaF_freeproto (lua_State *L, Proto *f) { + luaM_freearray(L, f->code, f->sizecode, Instruction); + luaM_freearray(L, f->p, f->sizep, Proto *); + luaM_freearray(L, f->k, f->sizek, TValue); + luaM_freearray(L, f->lineinfo, f->sizelineinfo, int); + luaM_freearray(L, f->locvars, f->sizelocvars, struct LocVar); + luaM_freearray(L, f->upvalues, f->sizeupvalues, TString *); + luaM_free(L, f); +} + + +void luaF_freeclosure (lua_State *L, Closure *c) { + int size = (c->c.isC) ? sizeCclosure(c->c.nupvalues) : + sizeLclosure(c->l.nupvalues); + luaM_freemem(L, c, size); +} + + +/* +** Look for n-th local variable at line `line' in function `func'. +** Returns NULL if not found. +*/ +const char *luaF_getlocalname (const Proto *f, int local_number, int pc) { + int i; + for (i = 0; i<f->sizelocvars && f->locvars[i].startpc <= pc; i++) { + if (pc < f->locvars[i].endpc) { /* is variable active? */ + local_number--; + if (local_number == 0) + return getstr(f->locvars[i].varname); + } + } + return NULL; /* not found */ +} + diff --git a/engines/sword25/util/lua/lfunc.h b/engines/sword25/util/lua/lfunc.h new file mode 100644 index 0000000000..4c2b7fd138 --- /dev/null +++ b/engines/sword25/util/lua/lfunc.h @@ -0,0 +1,34 @@ +/* +** $Id$ +** Auxiliary functions to manipulate prototypes and closures +** See Copyright Notice in lua.h +*/ + +#ifndef lfunc_h +#define lfunc_h + + +#include "lobject.h" + + +#define sizeCclosure(n) (cast(int, sizeof(CClosure)) + \ + cast(int, sizeof(TValue)*((n)-1))) + +#define sizeLclosure(n) (cast(int, sizeof(LClosure)) + \ + cast(int, sizeof(TValue *)*((n)-1))) + + +LUAI_FUNC Proto *luaF_newproto (lua_State *L); +LUAI_FUNC Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e); +LUAI_FUNC Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e); +LUAI_FUNC UpVal *luaF_newupval (lua_State *L); +LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); +LUAI_FUNC void luaF_close (lua_State *L, StkId level); +LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); +LUAI_FUNC void luaF_freeclosure (lua_State *L, Closure *c); +LUAI_FUNC void luaF_freeupval (lua_State *L, UpVal *uv); +LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, + int pc); + + +#endif diff --git a/engines/sword25/util/lua/lgc.cpp b/engines/sword25/util/lua/lgc.cpp new file mode 100644 index 0000000000..52ff72bdc9 --- /dev/null +++ b/engines/sword25/util/lua/lgc.cpp @@ -0,0 +1,711 @@ +/* +** $Id$ +** Garbage Collector +** See Copyright Notice in lua.h +*/ + +#include <string.h> + +#define lgc_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + +#define GCSTEPSIZE 1024u +#define GCSWEEPMAX 40 +#define GCSWEEPCOST 10 +#define GCFINALIZECOST 100 + + +#define maskmarks cast_byte(~(bitmask(BLACKBIT)|WHITEBITS)) + +#define makewhite(g,x) \ + ((x)->gch.marked = cast_byte(((x)->gch.marked & maskmarks) | luaC_white(g))) + +#define white2gray(x) reset2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT) +#define black2gray(x) resetbit((x)->gch.marked, BLACKBIT) + +#define stringmark(s) reset2bits((s)->tsv.marked, WHITE0BIT, WHITE1BIT) + + +#define isfinalized(u) testbit((u)->marked, FINALIZEDBIT) +#define markfinalized(u) l_setbit((u)->marked, FINALIZEDBIT) + + +#define KEYWEAK bitmask(KEYWEAKBIT) +#define VALUEWEAK bitmask(VALUEWEAKBIT) + + + +#define markvalue(g,o) { checkconsistency(o); \ + if (iscollectable(o) && iswhite(gcvalue(o))) reallymarkobject(g,gcvalue(o)); } + +#define markobject(g,t) { if (iswhite(obj2gco(t))) \ + reallymarkobject(g, obj2gco(t)); } + + +#define setthreshold(g) (g->GCthreshold = (g->estimate/100) * g->gcpause) + + +static void removeentry (Node *n) { + lua_assert(ttisnil(gval(n))); + if (iscollectable(gkey(n))) + setttype(gkey(n), LUA_TDEADKEY); /* dead key; remove it */ +} + + +static void reallymarkobject (global_State *g, GCObject *o) { + lua_assert(iswhite(o) && !isdead(g, o)); + white2gray(o); + switch (o->gch.tt) { + case LUA_TSTRING: { + return; + } + case LUA_TUSERDATA: { + Table *mt = gco2u(o)->metatable; + gray2black(o); /* udata are never gray */ + if (mt) markobject(g, mt); + markobject(g, gco2u(o)->env); + return; + } + case LUA_TUPVAL: { + UpVal *uv = gco2uv(o); + markvalue(g, uv->v); + if (uv->v == &uv->u.value) /* closed? */ + gray2black(o); /* open upvalues are never black */ + return; + } + case LUA_TFUNCTION: { + gco2cl(o)->c.gclist = g->gray; + g->gray = o; + break; + } + case LUA_TTABLE: { + gco2h(o)->gclist = g->gray; + g->gray = o; + break; + } + case LUA_TTHREAD: { + gco2th(o)->gclist = g->gray; + g->gray = o; + break; + } + case LUA_TPROTO: { + gco2p(o)->gclist = g->gray; + g->gray = o; + break; + } + default: lua_assert(0); + } +} + + +static void marktmu (global_State *g) { + GCObject *u = g->tmudata; + if (u) { + do { + u = u->gch.next; + makewhite(g, u); /* may be marked, if left from previous GC */ + reallymarkobject(g, u); + } while (u != g->tmudata); + } +} + + +/* move `dead' udata that need finalization to list `tmudata' */ +size_t luaC_separateudata (lua_State *L, int all) { + global_State *g = G(L); + size_t deadmem = 0; + GCObject **p = &g->mainthread->next; + GCObject *curr; + while ((curr = *p) != NULL) { + if (!(iswhite(curr) || all) || isfinalized(gco2u(curr))) + p = &curr->gch.next; /* don't bother with them */ + else if (fasttm(L, gco2u(curr)->metatable, TM_GC) == NULL) { + markfinalized(gco2u(curr)); /* don't need finalization */ + p = &curr->gch.next; + } + else { /* must call its gc method */ + deadmem += sizeudata(gco2u(curr)); + markfinalized(gco2u(curr)); + *p = curr->gch.next; + /* link `curr' at the end of `tmudata' list */ + if (g->tmudata == NULL) /* list is empty? */ + g->tmudata = curr->gch.next = curr; /* creates a circular list */ + else { + curr->gch.next = g->tmudata->gch.next; + g->tmudata->gch.next = curr; + g->tmudata = curr; + } + } + } + return deadmem; +} + + +static int traversetable (global_State *g, Table *h) { + int i; + int weakkey = 0; + int weakvalue = 0; + const TValue *mode; + if (h->metatable) + markobject(g, h->metatable); + mode = gfasttm(g, h->metatable, TM_MODE); + if (mode && ttisstring(mode)) { /* is there a weak mode? */ + weakkey = (strchr(svalue(mode), 'k') != NULL); + weakvalue = (strchr(svalue(mode), 'v') != NULL); + if (weakkey || weakvalue) { /* is really weak? */ + h->marked &= ~(KEYWEAK | VALUEWEAK); /* clear bits */ + h->marked |= cast_byte((weakkey << KEYWEAKBIT) | + (weakvalue << VALUEWEAKBIT)); + h->gclist = g->weak; /* must be cleared after GC, ... */ + g->weak = obj2gco(h); /* ... so put in the appropriate list */ + } + } + if (weakkey && weakvalue) return 1; + if (!weakvalue) { + i = h->sizearray; + while (i--) + markvalue(g, &h->array[i]); + } + i = sizenode(h); + while (i--) { + Node *n = gnode(h, i); + lua_assert(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n))); + if (ttisnil(gval(n))) + removeentry(n); /* remove empty entries */ + else { + lua_assert(!ttisnil(gkey(n))); + if (!weakkey) markvalue(g, gkey(n)); + if (!weakvalue) markvalue(g, gval(n)); + } + } + return weakkey || weakvalue; +} + + +/* +** All marks are conditional because a GC may happen while the +** prototype is still being created +*/ +static void traverseproto (global_State *g, Proto *f) { + int i; + if (f->source) stringmark(f->source); + for (i=0; i<f->sizek; i++) /* mark literals */ + markvalue(g, &f->k[i]); + for (i=0; i<f->sizeupvalues; i++) { /* mark upvalue names */ + if (f->upvalues[i]) + stringmark(f->upvalues[i]); + } + for (i=0; i<f->sizep; i++) { /* mark nested protos */ + if (f->p[i]) + markobject(g, f->p[i]); + } + for (i=0; i<f->sizelocvars; i++) { /* mark local-variable names */ + if (f->locvars[i].varname) + stringmark(f->locvars[i].varname); + } +} + + + +static void traverseclosure (global_State *g, Closure *cl) { + markobject(g, cl->c.env); + if (cl->c.isC) { + int i; + for (i=0; i<cl->c.nupvalues; i++) /* mark its upvalues */ + markvalue(g, &cl->c.upvalue[i]); + } + else { + int i; + lua_assert(cl->l.nupvalues == cl->l.p->nups); + markobject(g, cl->l.p); + for (i=0; i<cl->l.nupvalues; i++) /* mark its upvalues */ + markobject(g, cl->l.upvals[i]); + } +} + + +static void checkstacksizes (lua_State *L, StkId max) { + int ci_used = cast_int(L->ci - L->base_ci); /* number of `ci' in use */ + int s_used = cast_int(max - L->stack); /* part of stack in use */ + if (L->size_ci > LUAI_MAXCALLS) /* handling overflow? */ + return; /* do not touch the stacks */ + if (4*ci_used < L->size_ci && 2*BASIC_CI_SIZE < L->size_ci) + luaD_reallocCI(L, L->size_ci/2); /* still big enough... */ + condhardstacktests(luaD_reallocCI(L, ci_used + 1)); + if (4*s_used < L->stacksize && + 2*(BASIC_STACK_SIZE+EXTRA_STACK) < L->stacksize) + luaD_reallocstack(L, L->stacksize/2); /* still big enough... */ + condhardstacktests(luaD_reallocstack(L, s_used)); +} + + +static void traversestack (global_State *g, lua_State *l) { + StkId o, lim; + CallInfo *ci; + markvalue(g, gt(l)); + lim = l->top; + for (ci = l->base_ci; ci <= l->ci; ci++) { + lua_assert(ci->top <= l->stack_last); + if (lim < ci->top) lim = ci->top; + } + for (o = l->stack; o < l->top; o++) + markvalue(g, o); + for (; o <= lim; o++) + setnilvalue(o); + checkstacksizes(l, lim); +} + + +/* +** traverse one gray object, turning it to black. +** Returns `quantity' traversed. +*/ +static l_mem propagatemark (global_State *g) { + GCObject *o = g->gray; + lua_assert(isgray(o)); + gray2black(o); + switch (o->gch.tt) { + case LUA_TTABLE: { + Table *h = gco2h(o); + g->gray = h->gclist; + if (traversetable(g, h)) /* table is weak? */ + black2gray(o); /* keep it gray */ + return sizeof(Table) + sizeof(TValue) * h->sizearray + + sizeof(Node) * sizenode(h); + } + case LUA_TFUNCTION: { + Closure *cl = gco2cl(o); + g->gray = cl->c.gclist; + traverseclosure(g, cl); + return (cl->c.isC) ? sizeCclosure(cl->c.nupvalues) : + sizeLclosure(cl->l.nupvalues); + } + case LUA_TTHREAD: { + lua_State *th = gco2th(o); + g->gray = th->gclist; + th->gclist = g->grayagain; + g->grayagain = o; + black2gray(o); + traversestack(g, th); + return sizeof(lua_State) + sizeof(TValue) * th->stacksize + + sizeof(CallInfo) * th->size_ci; + } + case LUA_TPROTO: { + Proto *p = gco2p(o); + g->gray = p->gclist; + traverseproto(g, p); + return sizeof(Proto) + sizeof(Instruction) * p->sizecode + + sizeof(Proto *) * p->sizep + + sizeof(TValue) * p->sizek + + sizeof(int) * p->sizelineinfo + + sizeof(LocVar) * p->sizelocvars + + sizeof(TString *) * p->sizeupvalues; + } + default: lua_assert(0); return 0; + } +} + + +static size_t propagateall (global_State *g) { + size_t m = 0; + while (g->gray) m += propagatemark(g); + return m; +} + + +/* +** The next function tells whether a key or value can be cleared from +** a weak table. Non-collectable objects are never removed from weak +** tables. Strings behave as `values', so are never removed too. for +** other objects: if really collected, cannot keep them; for userdata +** being finalized, keep them in keys, but not in values +*/ +static int iscleared (const TValue *o, int iskey) { + if (!iscollectable(o)) return 0; + if (ttisstring(o)) { + stringmark(rawtsvalue(o)); /* strings are `values', so are never weak */ + return 0; + } + return iswhite(gcvalue(o)) || + (ttisuserdata(o) && (!iskey && isfinalized(uvalue(o)))); +} + + +/* +** clear collected entries from weaktables +*/ +static void cleartable (GCObject *l) { + while (l) { + Table *h = gco2h(l); + int i = h->sizearray; + lua_assert(testbit(h->marked, VALUEWEAKBIT) || + testbit(h->marked, KEYWEAKBIT)); + if (testbit(h->marked, VALUEWEAKBIT)) { + while (i--) { + TValue *o = &h->array[i]; + if (iscleared(o, 0)) /* value was collected? */ + setnilvalue(o); /* remove value */ + } + } + i = sizenode(h); + while (i--) { + Node *n = gnode(h, i); + if (!ttisnil(gval(n)) && /* non-empty entry? */ + (iscleared(key2tval(n), 1) || iscleared(gval(n), 0))) { + setnilvalue(gval(n)); /* remove value ... */ + removeentry(n); /* remove entry from table */ + } + } + l = h->gclist; + } +} + + +static void freeobj (lua_State *L, GCObject *o) { + switch (o->gch.tt) { + case LUA_TPROTO: luaF_freeproto(L, gco2p(o)); break; + case LUA_TFUNCTION: luaF_freeclosure(L, gco2cl(o)); break; + case LUA_TUPVAL: luaF_freeupval(L, gco2uv(o)); break; + case LUA_TTABLE: luaH_free(L, gco2h(o)); break; + case LUA_TTHREAD: { + lua_assert(gco2th(o) != L && gco2th(o) != G(L)->mainthread); + luaE_freethread(L, gco2th(o)); + break; + } + case LUA_TSTRING: { + G(L)->strt.nuse--; + luaM_freemem(L, o, sizestring(gco2ts(o))); + break; + } + case LUA_TUSERDATA: { + luaM_freemem(L, o, sizeudata(gco2u(o))); + break; + } + default: lua_assert(0); + } +} + + + +#define sweepwholelist(L,p) sweeplist(L,p,MAX_LUMEM) + + +static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { + GCObject *curr; + global_State *g = G(L); + int deadmask = otherwhite(g); + while ((curr = *p) != NULL && count-- > 0) { + if (curr->gch.tt == LUA_TTHREAD) /* sweep open upvalues of each thread */ + sweepwholelist(L, &gco2th(curr)->openupval); + if ((curr->gch.marked ^ WHITEBITS) & deadmask) { /* not dead? */ + lua_assert(!isdead(g, curr) || testbit(curr->gch.marked, FIXEDBIT)); + makewhite(g, curr); /* make it white (for next cycle) */ + p = &curr->gch.next; + } + else { /* must erase `curr' */ + lua_assert(isdead(g, curr) || deadmask == bitmask(SFIXEDBIT)); + *p = curr->gch.next; + if (curr == g->rootgc) /* is the first element of the list? */ + g->rootgc = curr->gch.next; /* adjust first */ + freeobj(L, curr); + } + } + return p; +} + + +static void checkSizes (lua_State *L) { + global_State *g = G(L); + /* check size of string hash */ + if (g->strt.nuse < cast(lu_int32, g->strt.size/4) && + g->strt.size > MINSTRTABSIZE*2) + luaS_resize(L, g->strt.size/2); /* table is too big */ + /* check size of buffer */ + if (luaZ_sizebuffer(&g->buff) > LUA_MINBUFFER*2) { /* buffer too big? */ + size_t newsize = luaZ_sizebuffer(&g->buff) / 2; + luaZ_resizebuffer(L, &g->buff, newsize); + } +} + + +static void GCTM (lua_State *L) { + global_State *g = G(L); + GCObject *o = g->tmudata->gch.next; /* get first element */ + Udata *udata = rawgco2u(o); + const TValue *tm; + /* remove udata from `tmudata' */ + if (o == g->tmudata) /* last element? */ + g->tmudata = NULL; + else + g->tmudata->gch.next = udata->uv.next; + udata->uv.next = g->mainthread->next; /* return it to `root' list */ + g->mainthread->next = o; + makewhite(g, o); + tm = fasttm(L, udata->uv.metatable, TM_GC); + if (tm != NULL) { + lu_byte oldah = L->allowhook; + lu_mem oldt = g->GCthreshold; + L->allowhook = 0; /* stop debug hooks during GC tag method */ + g->GCthreshold = 2*g->totalbytes; /* avoid GC steps */ + setobj2s(L, L->top, tm); + setuvalue(L, L->top+1, udata); + L->top += 2; + luaD_call(L, L->top - 2, 0); + L->allowhook = oldah; /* restore hooks */ + g->GCthreshold = oldt; /* restore threshold */ + } +} + + +/* +** Call all GC tag methods +*/ +void luaC_callGCTM (lua_State *L) { + while (G(L)->tmudata) + GCTM(L); +} + + +void luaC_freeall (lua_State *L) { + global_State *g = G(L); + int i; + g->currentwhite = WHITEBITS | bitmask(SFIXEDBIT); /* mask to collect all elements */ + sweepwholelist(L, &g->rootgc); + for (i = 0; i < g->strt.size; i++) /* free all string lists */ + sweepwholelist(L, &g->strt.hash[i]); +} + + +static void markmt (global_State *g) { + int i; + for (i=0; i<NUM_TAGS; i++) + if (g->mt[i]) markobject(g, g->mt[i]); +} + + +/* mark root set */ +static void markroot (lua_State *L) { + global_State *g = G(L); + g->gray = NULL; + g->grayagain = NULL; + g->weak = NULL; + markobject(g, g->mainthread); + /* make global table be traversed before main stack */ + markvalue(g, gt(g->mainthread)); + markvalue(g, registry(L)); + markmt(g); + g->gcstate = GCSpropagate; +} + + +static void remarkupvals (global_State *g) { + UpVal *uv; + for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) { + lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + if (isgray(obj2gco(uv))) + markvalue(g, uv->v); + } +} + + +static void atomic (lua_State *L) { + global_State *g = G(L); + size_t udsize; /* total size of userdata to be finalized */ + /* remark occasional upvalues of (maybe) dead threads */ + remarkupvals(g); + /* traverse objects cautch by write barrier and by 'remarkupvals' */ + propagateall(g); + /* remark weak tables */ + g->gray = g->weak; + g->weak = NULL; + lua_assert(!iswhite(obj2gco(g->mainthread))); + markobject(g, L); /* mark running thread */ + markmt(g); /* mark basic metatables (again) */ + propagateall(g); + /* remark gray again */ + g->gray = g->grayagain; + g->grayagain = NULL; + propagateall(g); + udsize = luaC_separateudata(L, 0); /* separate userdata to be finalized */ + marktmu(g); /* mark `preserved' userdata */ + udsize += propagateall(g); /* remark, to propagate `preserveness' */ + cleartable(g->weak); /* remove collected objects from weak tables */ + /* flip current white */ + g->currentwhite = cast_byte(otherwhite(g)); + g->sweepstrgc = 0; + g->sweepgc = &g->rootgc; + g->gcstate = GCSsweepstring; + g->estimate = g->totalbytes - udsize; /* first estimate */ +} + + +static l_mem singlestep (lua_State *L) { + global_State *g = G(L); + /*lua_checkmemory(L);*/ + switch (g->gcstate) { + case GCSpause: { + markroot(L); /* start a new collection */ + return 0; + } + case GCSpropagate: { + if (g->gray) + return propagatemark(g); + else { /* no more `gray' objects */ + atomic(L); /* finish mark phase */ + return 0; + } + } + case GCSsweepstring: { + lu_mem old = g->totalbytes; + sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]); + if (g->sweepstrgc >= g->strt.size) /* nothing more to sweep? */ + g->gcstate = GCSsweep; /* end sweep-string phase */ + lua_assert(old >= g->totalbytes); + g->estimate -= old - g->totalbytes; + return GCSWEEPCOST; + } + case GCSsweep: { + lu_mem old = g->totalbytes; + g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); + if (*g->sweepgc == NULL) { /* nothing more to sweep? */ + checkSizes(L); + g->gcstate = GCSfinalize; /* end sweep phase */ + } + lua_assert(old >= g->totalbytes); + g->estimate -= old - g->totalbytes; + return GCSWEEPMAX*GCSWEEPCOST; + } + case GCSfinalize: { + if (g->tmudata) { + GCTM(L); + if (g->estimate > GCFINALIZECOST) + g->estimate -= GCFINALIZECOST; + return GCFINALIZECOST; + } + else { + g->gcstate = GCSpause; /* end collection */ + g->gcdept = 0; + return 0; + } + } + default: lua_assert(0); return 0; + } +} + + +void luaC_step (lua_State *L) { + global_State *g = G(L); + l_mem lim = (GCSTEPSIZE/100) * g->gcstepmul; + if (lim == 0) + lim = (MAX_LUMEM-1)/2; /* no limit */ + g->gcdept += g->totalbytes - g->GCthreshold; + do { + lim -= singlestep(L); + if (g->gcstate == GCSpause) + break; + } while (lim > 0); + if (g->gcstate != GCSpause) { + if (g->gcdept < GCSTEPSIZE) + g->GCthreshold = g->totalbytes + GCSTEPSIZE; /* - lim/g->gcstepmul;*/ + else { + g->gcdept -= GCSTEPSIZE; + g->GCthreshold = g->totalbytes; + } + } + else { + lua_assert(g->totalbytes >= g->estimate); + setthreshold(g); + } +} + + +void luaC_fullgc (lua_State *L) { + global_State *g = G(L); + if (g->gcstate <= GCSpropagate) { + /* reset sweep marks to sweep all elements (returning them to white) */ + g->sweepstrgc = 0; + g->sweepgc = &g->rootgc; + /* reset other collector lists */ + g->gray = NULL; + g->grayagain = NULL; + g->weak = NULL; + g->gcstate = GCSsweepstring; + } + lua_assert(g->gcstate != GCSpause && g->gcstate != GCSpropagate); + /* finish any pending sweep phase */ + while (g->gcstate != GCSfinalize) { + lua_assert(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep); + singlestep(L); + } + markroot(L); + while (g->gcstate != GCSpause) { + singlestep(L); + } + setthreshold(g); +} + + +void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v) { + global_State *g = G(L); + lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); + lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); + lua_assert(ttype(&o->gch) != LUA_TTABLE); + /* must keep invariant? */ + if (g->gcstate == GCSpropagate) + reallymarkobject(g, v); /* restore invariant */ + else /* don't mind */ + makewhite(g, o); /* mark as white just to avoid other barriers */ +} + + +void luaC_barrierback (lua_State *L, Table *t) { + global_State *g = G(L); + GCObject *o = obj2gco(t); + lua_assert(isblack(o) && !isdead(g, o)); + lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); + black2gray(o); /* make table gray (again) */ + t->gclist = g->grayagain; + g->grayagain = o; +} + + +void luaC_link (lua_State *L, GCObject *o, lu_byte tt) { + global_State *g = G(L); + o->gch.next = g->rootgc; + g->rootgc = o; + o->gch.marked = luaC_white(g); + o->gch.tt = tt; +} + + +void luaC_linkupval (lua_State *L, UpVal *uv) { + global_State *g = G(L); + GCObject *o = obj2gco(uv); + o->gch.next = g->rootgc; /* link upvalue into `rootgc' list */ + g->rootgc = o; + if (isgray(o)) { + if (g->gcstate == GCSpropagate) { + gray2black(o); /* closed upvalues need barrier */ + luaC_barrier(L, uv, uv->v); + } + else { /* sweep phase: sweep it (turning it into white) */ + makewhite(g, o); + lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); + } + } +} + diff --git a/engines/sword25/util/lua/lgc.h b/engines/sword25/util/lua/lgc.h new file mode 100644 index 0000000000..5123ccb479 --- /dev/null +++ b/engines/sword25/util/lua/lgc.h @@ -0,0 +1,110 @@ +/* +** $Id$ +** Garbage Collector +** See Copyright Notice in lua.h +*/ + +#ifndef lgc_h +#define lgc_h + + +#include "lobject.h" + + +/* +** Possible states of the Garbage Collector +*/ +#define GCSpause 0 +#define GCSpropagate 1 +#define GCSsweepstring 2 +#define GCSsweep 3 +#define GCSfinalize 4 + + +/* +** some userful bit tricks +*/ +#define resetbits(x,m) ((x) &= cast(lu_byte, ~(m))) +#define setbits(x,m) ((x) |= (m)) +#define testbits(x,m) ((x) & (m)) +#define bitmask(b) (1<<(b)) +#define bit2mask(b1,b2) (bitmask(b1) | bitmask(b2)) +#define l_setbit(x,b) setbits(x, bitmask(b)) +#define resetbit(x,b) resetbits(x, bitmask(b)) +#define testbit(x,b) testbits(x, bitmask(b)) +#define set2bits(x,b1,b2) setbits(x, (bit2mask(b1, b2))) +#define reset2bits(x,b1,b2) resetbits(x, (bit2mask(b1, b2))) +#define test2bits(x,b1,b2) testbits(x, (bit2mask(b1, b2))) + + + +/* +** Layout for bit use in `marked' field: +** bit 0 - object is white (type 0) +** bit 1 - object is white (type 1) +** bit 2 - object is black +** bit 3 - for userdata: has been finalized +** bit 3 - for tables: has weak keys +** bit 4 - for tables: has weak values +** bit 5 - object is fixed (should not be collected) +** bit 6 - object is "super" fixed (only the main thread) +*/ + + +#define WHITE0BIT 0 +#define WHITE1BIT 1 +#define BLACKBIT 2 +#define FINALIZEDBIT 3 +#define KEYWEAKBIT 3 +#define VALUEWEAKBIT 4 +#define FIXEDBIT 5 +#define SFIXEDBIT 6 +#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) + + +#define iswhite(x) test2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT) +#define isblack(x) testbit((x)->gch.marked, BLACKBIT) +#define isgray(x) (!isblack(x) && !iswhite(x)) + +#define otherwhite(g) (g->currentwhite ^ WHITEBITS) +#define isdead(g,v) ((v)->gch.marked & otherwhite(g) & WHITEBITS) + +#define changewhite(x) ((x)->gch.marked ^= WHITEBITS) +#define gray2black(x) l_setbit((x)->gch.marked, BLACKBIT) + +#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) + +#define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS) + + +#define luaC_checkGC(L) { \ + condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); \ + if (G(L)->totalbytes >= G(L)->GCthreshold) \ + luaC_step(L); } + + +#define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ + luaC_barrierf(L,obj2gco(p),gcvalue(v)); } + +#define luaC_barriert(L,t,v) { if (valiswhite(v) && isblack(obj2gco(t))) \ + luaC_barrierback(L,t); } + +#define luaC_objbarrier(L,p,o) \ + { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \ + luaC_barrierf(L,obj2gco(p),obj2gco(o)); } + +#define luaC_objbarriert(L,t,o) \ + { if (iswhite(obj2gco(o)) && isblack(obj2gco(t))) luaC_barrierback(L,t); } + +LUAI_FUNC size_t luaC_separateudata (lua_State *L, int all); +LUAI_FUNC void luaC_callGCTM (lua_State *L); +LUAI_FUNC void luaC_freeall (lua_State *L); +LUAI_FUNC void luaC_step (lua_State *L); +LUAI_FUNC void luaC_fullgc (lua_State *L); +LUAI_FUNC void luaC_link (lua_State *L, GCObject *o, lu_byte tt); +LUAI_FUNC void luaC_linkupval (lua_State *L, UpVal *uv); +LUAI_FUNC void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v); +LUAI_FUNC void luaC_barrierback (lua_State *L, Table *t); + + +#endif diff --git a/engines/sword25/util/lua/linit.cpp b/engines/sword25/util/lua/linit.cpp new file mode 100644 index 0000000000..93f41d0350 --- /dev/null +++ b/engines/sword25/util/lua/linit.cpp @@ -0,0 +1,38 @@ +/* +** $Id$ +** Initialization of libraries for lua.c +** See Copyright Notice in lua.h +*/ + + +#define linit_c +#define LUA_LIB + +#include "lua.h" + +#include "lualib.h" +#include "lauxlib.h" + + +static const luaL_Reg lualibs[] = { + {"", luaopen_base}, + {LUA_LOADLIBNAME, luaopen_package}, + {LUA_TABLIBNAME, luaopen_table}, + {LUA_IOLIBNAME, luaopen_io}, + {LUA_OSLIBNAME, luaopen_os}, + {LUA_STRLIBNAME, luaopen_string}, + {LUA_MATHLIBNAME, luaopen_math}, + {LUA_DBLIBNAME, luaopen_debug}, + {NULL, NULL} +}; + + +LUALIB_API void luaL_openlibs (lua_State *L) { + const luaL_Reg *lib = lualibs; + for (; lib->func; lib++) { + lua_pushcfunction(L, lib->func); + lua_pushstring(L, lib->name); + lua_call(L, 1, 0); + } +} + diff --git a/engines/sword25/util/lua/liolib.cpp b/engines/sword25/util/lua/liolib.cpp new file mode 100644 index 0000000000..aa44dcafa3 --- /dev/null +++ b/engines/sword25/util/lua/liolib.cpp @@ -0,0 +1,553 @@ +/* +** $Id$ +** Standard I/O (and system) library +** See Copyright Notice in lua.h +*/ + + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define liolib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + + +#define IO_INPUT 1 +#define IO_OUTPUT 2 + + +static const char *const fnames[] = {"input", "output"}; + + +static int pushresult (lua_State *L, int i, const char *filename) { + int en = errno; /* calls to Lua API may change this value */ + if (i) { + lua_pushboolean(L, 1); + return 1; + } + else { + lua_pushnil(L); + if (filename) + lua_pushfstring(L, "%s: %s", filename, strerror(en)); + else + lua_pushfstring(L, "%s", strerror(en)); + lua_pushinteger(L, en); + return 3; + } +} + + +static void fileerror (lua_State *L, int arg, const char *filename) { + lua_pushfstring(L, "%s: %s", filename, strerror(errno)); + luaL_argerror(L, arg, lua_tostring(L, -1)); +} + + +#define tofilep(L) ((FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE)) + + +static int io_type (lua_State *L) { + void *ud; + luaL_checkany(L, 1); + ud = lua_touserdata(L, 1); + lua_getfield(L, LUA_REGISTRYINDEX, LUA_FILEHANDLE); + if (ud == NULL || !lua_getmetatable(L, 1) || !lua_rawequal(L, -2, -1)) + lua_pushnil(L); /* not a file */ + else if (*((FILE **)ud) == NULL) + lua_pushliteral(L, "closed file"); + else + lua_pushliteral(L, "file"); + return 1; +} + + +static FILE *tofile (lua_State *L) { + FILE **f = tofilep(L); + if (*f == NULL) + luaL_error(L, "attempt to use a closed file"); + return *f; +} + + + +/* +** When creating file handles, always creates a `closed' file handle +** before opening the actual file; so, if there is a memory error, the +** file is not left opened. +*/ +static FILE **newfile (lua_State *L) { + FILE **pf = (FILE **)lua_newuserdata(L, sizeof(FILE *)); + *pf = NULL; /* file handle is currently `closed' */ + luaL_getmetatable(L, LUA_FILEHANDLE); + lua_setmetatable(L, -2); + return pf; +} + + +/* +** function to (not) close the standard files stdin, stdout, and stderr +*/ +static int io_noclose (lua_State *L) { + lua_pushnil(L); + lua_pushliteral(L, "cannot close standard file"); + return 2; +} + + +/* +** function to close 'popen' files +*/ +static int io_pclose (lua_State *L) { + FILE **p = tofilep(L); + int ok = lua_pclose(L, *p); + *p = NULL; + return pushresult(L, ok, NULL); +} + + +/* +** function to close regular files +*/ +static int io_fclose (lua_State *L) { + FILE **p = tofilep(L); + int ok = (fclose(*p) == 0); + *p = NULL; + return pushresult(L, ok, NULL); +} + + +static int aux_close (lua_State *L) { + lua_getfenv(L, 1); + lua_getfield(L, -1, "__close"); + return (lua_tocfunction(L, -1))(L); +} + + +static int io_close (lua_State *L) { + if (lua_isnone(L, 1)) + lua_rawgeti(L, LUA_ENVIRONINDEX, IO_OUTPUT); + tofile(L); /* make sure argument is a file */ + return aux_close(L); +} + + +static int io_gc (lua_State *L) { + FILE *f = *tofilep(L); + /* ignore closed files */ + if (f != NULL) + aux_close(L); + return 0; +} + + +static int io_tostring (lua_State *L) { + FILE *f = *tofilep(L); + if (f == NULL) + lua_pushliteral(L, "file (closed)"); + else + lua_pushfstring(L, "file (%p)", f); + return 1; +} + + +static int io_open (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + FILE **pf = newfile(L); + *pf = fopen(filename, mode); + return (*pf == NULL) ? pushresult(L, 0, filename) : 1; +} + + +/* +** this function has a separated environment, which defines the +** correct __close for 'popen' files +*/ +static int io_popen (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + const char *mode = luaL_optstring(L, 2, "r"); + FILE **pf = newfile(L); + *pf = lua_popen(L, filename, mode); + return (*pf == NULL) ? pushresult(L, 0, filename) : 1; +} + + +static int io_tmpfile (lua_State *L) { + FILE **pf = newfile(L); + *pf = tmpfile(); + return (*pf == NULL) ? pushresult(L, 0, NULL) : 1; +} + + +static FILE *getiofile (lua_State *L, int findex) { + FILE *f; + lua_rawgeti(L, LUA_ENVIRONINDEX, findex); + f = *(FILE **)lua_touserdata(L, -1); + if (f == NULL) + luaL_error(L, "standard %s file is closed", fnames[findex - 1]); + return f; +} + + +static int g_iofile (lua_State *L, int f, const char *mode) { + if (!lua_isnoneornil(L, 1)) { + const char *filename = lua_tostring(L, 1); + if (filename) { + FILE **pf = newfile(L); + *pf = fopen(filename, mode); + if (*pf == NULL) + fileerror(L, 1, filename); + } + else { + tofile(L); /* check that it's a valid file handle */ + lua_pushvalue(L, 1); + } + lua_rawseti(L, LUA_ENVIRONINDEX, f); + } + /* return current value */ + lua_rawgeti(L, LUA_ENVIRONINDEX, f); + return 1; +} + + +static int io_input (lua_State *L) { + return g_iofile(L, IO_INPUT, "r"); +} + + +static int io_output (lua_State *L) { + return g_iofile(L, IO_OUTPUT, "w"); +} + + +static int io_readline (lua_State *L); + + +static void aux_lines (lua_State *L, int idx, int toclose) { + lua_pushvalue(L, idx); + lua_pushboolean(L, toclose); /* close/not close file when finished */ + lua_pushcclosure(L, io_readline, 2); +} + + +static int f_lines (lua_State *L) { + tofile(L); /* check that it's a valid file handle */ + aux_lines(L, 1, 0); + return 1; +} + + +static int io_lines (lua_State *L) { + if (lua_isnoneornil(L, 1)) { /* no arguments? */ + /* will iterate over default input */ + lua_rawgeti(L, LUA_ENVIRONINDEX, IO_INPUT); + return f_lines(L); + } + else { + const char *filename = luaL_checkstring(L, 1); + FILE **pf = newfile(L); + *pf = fopen(filename, "r"); + if (*pf == NULL) + fileerror(L, 1, filename); + aux_lines(L, lua_gettop(L), 1); + return 1; + } +} + + +/* +** {====================================================== +** READ +** ======================================================= +*/ + + +static int read_number (lua_State *L, FILE *f) { + lua_Number d; + if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) { + lua_pushnumber(L, d); + return 1; + } + else return 0; /* read fails */ +} + + +static int test_eof (lua_State *L, FILE *f) { + int c = getc(f); + ungetc(c, f); + lua_pushlstring(L, NULL, 0); + return (c != EOF); +} + + +static int read_line (lua_State *L, FILE *f) { + luaL_Buffer b; + luaL_buffinit(L, &b); + for (;;) { + size_t l; + char *p = luaL_prepbuffer(&b); + if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) { /* eof? */ + luaL_pushresult(&b); /* close buffer */ + return (lua_objlen(L, -1) > 0); /* check whether read something */ + } + l = strlen(p); + if (l == 0 || p[l-1] != '\n') + luaL_addsize(&b, l); + else { + luaL_addsize(&b, l - 1); /* do not include `eol' */ + luaL_pushresult(&b); /* close buffer */ + return 1; /* read at least an `eol' */ + } + } +} + + +static int read_chars (lua_State *L, FILE *f, size_t n) { + size_t rlen; /* how much to read */ + size_t nr; /* number of chars actually read */ + luaL_Buffer b; + luaL_buffinit(L, &b); + rlen = LUAL_BUFFERSIZE; /* try to read that much each time */ + do { + char *p = luaL_prepbuffer(&b); + if (rlen > n) rlen = n; /* cannot read more than asked */ + nr = fread(p, sizeof(char), rlen, f); + luaL_addsize(&b, nr); + n -= nr; /* still have to read `n' chars */ + } while (n > 0 && nr == rlen); /* until end of count or eof */ + luaL_pushresult(&b); /* close buffer */ + return (n == 0 || lua_objlen(L, -1) > 0); +} + + +static int g_read (lua_State *L, FILE *f, int first) { + int nargs = lua_gettop(L) - 1; + int success; + int n; + clearerr(f); + if (nargs == 0) { /* no arguments? */ + success = read_line(L, f); + n = first+1; /* to return 1 result */ + } + else { /* ensure stack space for all results and for auxlib's buffer */ + luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); + success = 1; + for (n = first; nargs-- && success; n++) { + if (lua_type(L, n) == LUA_TNUMBER) { + size_t l = (size_t)lua_tointeger(L, n); + success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); + } + else { + const char *p = lua_tostring(L, n); + luaL_argcheck(L, p && p[0] == '*', n, "invalid option"); + switch (p[1]) { + case 'n': /* number */ + success = read_number(L, f); + break; + case 'l': /* line */ + success = read_line(L, f); + break; + case 'a': /* file */ + read_chars(L, f, ~((size_t)0)); /* read MAX_SIZE_T chars */ + success = 1; /* always success */ + break; + default: + return luaL_argerror(L, n, "invalid format"); + } + } + } + } + if (ferror(f)) + return pushresult(L, 0, NULL); + if (!success) { + lua_pop(L, 1); /* remove last result */ + lua_pushnil(L); /* push nil instead */ + } + return n - first; +} + + +static int io_read (lua_State *L) { + return g_read(L, getiofile(L, IO_INPUT), 1); +} + + +static int f_read (lua_State *L) { + return g_read(L, tofile(L), 2); +} + + +static int io_readline (lua_State *L) { + FILE *f = *(FILE **)lua_touserdata(L, lua_upvalueindex(1)); + int sucess; + if (f == NULL) /* file is already closed? */ + luaL_error(L, "file is already closed"); + sucess = read_line(L, f); + if (ferror(f)) + return luaL_error(L, "%s", strerror(errno)); + if (sucess) return 1; + else { /* EOF */ + if (lua_toboolean(L, lua_upvalueindex(2))) { /* generator created file? */ + lua_settop(L, 0); + lua_pushvalue(L, lua_upvalueindex(1)); + aux_close(L); /* close it */ + } + return 0; + } +} + +/* }====================================================== */ + + +static int g_write (lua_State *L, FILE *f, int arg) { + int nargs = lua_gettop(L) - 1; + int status = 1; + for (; nargs--; arg++) { + if (lua_type(L, arg) == LUA_TNUMBER) { + /* optimization: could be done exactly as for strings */ + status = status && + fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0; + } + else { + size_t l; + const char *s = luaL_checklstring(L, arg, &l); + status = status && (fwrite(s, sizeof(char), l, f) == l); + } + } + return pushresult(L, status, NULL); +} + + +static int io_write (lua_State *L) { + return g_write(L, getiofile(L, IO_OUTPUT), 1); +} + + +static int f_write (lua_State *L) { + return g_write(L, tofile(L), 2); +} + + +static int f_seek (lua_State *L) { + static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; + static const char *const modenames[] = {"set", "cur", "end", NULL}; + FILE *f = tofile(L); + int op = luaL_checkoption(L, 2, "cur", modenames); + long offset = luaL_optlong(L, 3, 0); + op = fseek(f, offset, mode[op]); + if (op) + return pushresult(L, 0, NULL); /* error */ + else { + lua_pushinteger(L, ftell(f)); + return 1; + } +} + + +static int f_setvbuf (lua_State *L) { + static const int mode[] = {_IONBF, _IOFBF, _IOLBF}; + static const char *const modenames[] = {"no", "full", "line", NULL}; + FILE *f = tofile(L); + int op = luaL_checkoption(L, 2, NULL, modenames); + lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); + int res = setvbuf(f, NULL, mode[op], sz); + return pushresult(L, res == 0, NULL); +} + + + +static int io_flush (lua_State *L) { + return pushresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL); +} + + +static int f_flush (lua_State *L) { + return pushresult(L, fflush(tofile(L)) == 0, NULL); +} + + +static const luaL_Reg iolib[] = { + {"close", io_close}, + {"flush", io_flush}, + {"input", io_input}, + {"lines", io_lines}, + {"open", io_open}, + {"output", io_output}, + {"popen", io_popen}, + {"read", io_read}, + {"tmpfile", io_tmpfile}, + {"type", io_type}, + {"write", io_write}, + {NULL, NULL} +}; + + +static const luaL_Reg flib[] = { + {"close", io_close}, + {"flush", f_flush}, + {"lines", f_lines}, + {"read", f_read}, + {"seek", f_seek}, + {"setvbuf", f_setvbuf}, + {"write", f_write}, + {"__gc", io_gc}, + {"__tostring", io_tostring}, + {NULL, NULL} +}; + + +static void createmeta (lua_State *L) { + luaL_newmetatable(L, LUA_FILEHANDLE); /* create metatable for file handles */ + lua_pushvalue(L, -1); /* push metatable */ + lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */ + luaL_register(L, NULL, flib); /* file methods */ +} + + +static void createstdfile (lua_State *L, FILE *f, int k, const char *fname) { + *newfile(L) = f; + if (k > 0) { + lua_pushvalue(L, -1); + lua_rawseti(L, LUA_ENVIRONINDEX, k); + } + lua_pushvalue(L, -2); /* copy environment */ + lua_setfenv(L, -2); /* set it */ + lua_setfield(L, -3, fname); +} + + +static void newfenv (lua_State *L, lua_CFunction cls) { + lua_createtable(L, 0, 1); + lua_pushcfunction(L, cls); + lua_setfield(L, -2, "__close"); +} + + +LUALIB_API int luaopen_io (lua_State *L) { + createmeta(L); + /* create (private) environment (with fields IO_INPUT, IO_OUTPUT, __close) */ + newfenv(L, io_fclose); + lua_replace(L, LUA_ENVIRONINDEX); + /* open library */ + luaL_register(L, LUA_IOLIBNAME, iolib); + /* create (and set) default files */ + newfenv(L, io_noclose); /* close function for default files */ + createstdfile(L, stdin, IO_INPUT, "stdin"); + createstdfile(L, stdout, IO_OUTPUT, "stdout"); + createstdfile(L, stderr, 0, "stderr"); + lua_pop(L, 1); /* pop environment for default files */ + lua_getfield(L, -1, "popen"); + newfenv(L, io_pclose); /* create environment for 'popen' */ + lua_setfenv(L, -2); /* set fenv for 'popen' */ + lua_pop(L, 1); /* pop 'popen' */ + return 1; +} + diff --git a/engines/sword25/util/lua/llex.cpp b/engines/sword25/util/lua/llex.cpp new file mode 100644 index 0000000000..fdde2b8e5f --- /dev/null +++ b/engines/sword25/util/lua/llex.cpp @@ -0,0 +1,461 @@ +/* +** $Id$ +** Lexical Analyzer +** See Copyright Notice in lua.h +*/ + + +#include <ctype.h> +#include <locale.h> +#include <string.h> + +#define llex_c +#define LUA_CORE + +#include "lua.h" + +#include "ldo.h" +#include "llex.h" +#include "lobject.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "lzio.h" + + + +#define next(ls) (ls->current = zgetc(ls->z)) + + + + +#define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') + + +/* ORDER RESERVED */ +const char *const luaX_tokens [] = { + "and", "break", "do", "else", "elseif", + "end", "false", "for", "function", "if", + "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while", + "..", "...", "==", ">=", "<=", "~=", + "<number>", "<name>", "<string>", "<eof>", + NULL +}; + + +#define save_and_next(ls) (save(ls, ls->current), next(ls)) + + +static void save (LexState *ls, int c) { + Mbuffer *b = ls->buff; + if (b->n + 1 > b->buffsize) { + size_t newsize; + if (b->buffsize >= MAX_SIZET/2) + luaX_lexerror(ls, "lexical element too long", 0); + newsize = b->buffsize * 2; + luaZ_resizebuffer(ls->L, b, newsize); + } + b->buffer[b->n++] = cast(char, c); +} + + +void luaX_init (lua_State *L) { + int i; + for (i=0; i<NUM_RESERVED; i++) { + TString *ts = luaS_new(L, luaX_tokens[i]); + luaS_fix(ts); /* reserved words are never collected */ + lua_assert(strlen(luaX_tokens[i])+1 <= TOKEN_LEN); + ts->tsv.reserved = cast_byte(i+1); /* reserved word */ + } +} + + +#define MAXSRC 80 + + +const char *luaX_token2str (LexState *ls, int token) { + if (token < FIRST_RESERVED) { + lua_assert(token == cast(unsigned char, token)); + return (iscntrl(token)) ? luaO_pushfstring(ls->L, "char(%d)", token) : + luaO_pushfstring(ls->L, "%c", token); + } + else + return luaX_tokens[token-FIRST_RESERVED]; +} + + +static const char *txtToken (LexState *ls, int token) { + switch (token) { + case TK_NAME: + case TK_STRING: + case TK_NUMBER: + save(ls, '\0'); + return luaZ_buffer(ls->buff); + default: + return luaX_token2str(ls, token); + } +} + + +void luaX_lexerror (LexState *ls, const char *msg, int token) { + char buff[MAXSRC]; + luaO_chunkid(buff, getstr(ls->source), MAXSRC); + msg = luaO_pushfstring(ls->L, "%s:%d: %s", buff, ls->linenumber, msg); + if (token) + luaO_pushfstring(ls->L, "%s near " LUA_QS, msg, txtToken(ls, token)); + luaD_throw(ls->L, LUA_ERRSYNTAX); +} + + +void luaX_syntaxerror (LexState *ls, const char *msg) { + luaX_lexerror(ls, msg, ls->t.token); +} + + +TString *luaX_newstring (LexState *ls, const char *str, size_t l) { + lua_State *L = ls->L; + TString *ts = luaS_newlstr(L, str, l); + TValue *o = luaH_setstr(L, ls->fs->h, ts); /* entry for `str' */ + if (ttisnil(o)) + setbvalue(o, 1); /* make sure `str' will not be collected */ + return ts; +} + + +static void inclinenumber (LexState *ls) { + int old = ls->current; + lua_assert(currIsNewline(ls)); + next(ls); /* skip `\n' or `\r' */ + if (currIsNewline(ls) && ls->current != old) + next(ls); /* skip `\n\r' or `\r\n' */ + if (++ls->linenumber >= MAX_INT) + luaX_syntaxerror(ls, "chunk has too many lines"); +} + + +void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source) { + ls->decpoint = '.'; + ls->L = L; + ls->lookahead.token = TK_EOS; /* no look-ahead token */ + ls->z = z; + ls->fs = NULL; + ls->linenumber = 1; + ls->lastline = 1; + ls->source = source; + luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ + next(ls); /* read first char */ +} + + + +/* +** ======================================================= +** LEXICAL ANALYZER +** ======================================================= +*/ + + + +static int check_next (LexState *ls, const char *set) { + if (!strchr(set, ls->current)) + return 0; + save_and_next(ls); + return 1; +} + + +static void buffreplace (LexState *ls, char from, char to) { + size_t n = luaZ_bufflen(ls->buff); + char *p = luaZ_buffer(ls->buff); + while (n--) + if (p[n] == from) p[n] = to; +} + + +static void trydecpoint (LexState *ls, SemInfo *seminfo) { + /* format error: try to update decimal point separator */ + struct lconv *cv = localeconv(); + char old = ls->decpoint; + ls->decpoint = (cv ? cv->decimal_point[0] : '.'); + buffreplace(ls, old, ls->decpoint); /* try updated decimal separator */ + if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) { + /* format error with correct decimal point: no more options */ + buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */ + luaX_lexerror(ls, "malformed number", TK_NUMBER); + } +} + + +/* LUA_NUMBER */ +static void read_numeral (LexState *ls, SemInfo *seminfo) { + lua_assert(isdigit(ls->current)); + do { + save_and_next(ls); + } while (isdigit(ls->current) || ls->current == '.'); + if (check_next(ls, "Ee")) /* `E'? */ + check_next(ls, "+-"); /* optional exponent sign */ + while (isalnum(ls->current) || ls->current == '_') + save_and_next(ls); + save(ls, '\0'); + buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ + if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) /* format error? */ + trydecpoint(ls, seminfo); /* try to update decimal point separator */ +} + + +static int skip_sep (LexState *ls) { + int count = 0; + int s = ls->current; + lua_assert(s == '[' || s == ']'); + save_and_next(ls); + while (ls->current == '=') { + save_and_next(ls); + count++; + } + return (ls->current == s) ? count : (-count) - 1; +} + + +static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { + int cont = 0; + (void)(cont); /* avoid warnings when `cont' is not used */ + save_and_next(ls); /* skip 2nd `[' */ + if (currIsNewline(ls)) /* string starts with a newline? */ + inclinenumber(ls); /* skip it */ + for (;;) { + switch (ls->current) { + case EOZ: + luaX_lexerror(ls, (seminfo) ? "unfinished long string" : + "unfinished long comment", TK_EOS); + break; /* to avoid warnings */ +#if defined(LUA_COMPAT_LSTR) + case '[': { + if (skip_sep(ls) == sep) { + save_and_next(ls); /* skip 2nd `[' */ + cont++; +#if LUA_COMPAT_LSTR == 1 + if (sep == 0) + luaX_lexerror(ls, "nesting of [[...]] is deprecated", '['); +#endif + } + break; + } +#endif + case ']': { + if (skip_sep(ls) == sep) { + save_and_next(ls); /* skip 2nd `]' */ +#if defined(LUA_COMPAT_LSTR) && LUA_COMPAT_LSTR == 2 + cont--; + if (sep == 0 && cont >= 0) break; +#endif + goto endloop; + } + break; + } + case '\n': + case '\r': { + save(ls, '\n'); + inclinenumber(ls); + if (!seminfo) luaZ_resetbuffer(ls->buff); /* avoid wasting space */ + break; + } + default: { + if (seminfo) save_and_next(ls); + else next(ls); + } + } + } endloop: + if (seminfo) + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep), + luaZ_bufflen(ls->buff) - 2*(2 + sep)); +} + + +static void read_string (LexState *ls, int del, SemInfo *seminfo) { + save_and_next(ls); + while (ls->current != del) { + switch (ls->current) { + case EOZ: + luaX_lexerror(ls, "unfinished string", TK_EOS); + continue; /* to avoid warnings */ + case '\n': + case '\r': + luaX_lexerror(ls, "unfinished string", TK_STRING); + continue; /* to avoid warnings */ + case '\\': { + int c; + next(ls); /* do not save the `\' */ + switch (ls->current) { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\n': /* go through */ + case '\r': save(ls, '\n'); inclinenumber(ls); continue; + case EOZ: continue; /* will raise an error next loop */ + default: { + if (!isdigit(ls->current)) + save_and_next(ls); /* handles \\, \", \', and \? */ + else { /* \xxx */ + int i = 0; + c = 0; + do { + c = 10*c + (ls->current-'0'); + next(ls); + } while (++i<3 && isdigit(ls->current)); + if (c > UCHAR_MAX) + luaX_lexerror(ls, "escape sequence too large", TK_STRING); + save(ls, c); + } + continue; + } + } + save(ls, c); + next(ls); + continue; + } + default: + save_and_next(ls); + } + } + save_and_next(ls); /* skip delimiter */ + seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + 1, + luaZ_bufflen(ls->buff) - 2); +} + + +static int llex (LexState *ls, SemInfo *seminfo) { + luaZ_resetbuffer(ls->buff); + for (;;) { + switch (ls->current) { + case '\n': + case '\r': { + inclinenumber(ls); + continue; + } + case '-': { + next(ls); + if (ls->current != '-') return '-'; + /* else is a comment */ + next(ls); + if (ls->current == '[') { + int sep = skip_sep(ls); + luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */ + if (sep >= 0) { + read_long_string(ls, NULL, sep); /* long comment */ + luaZ_resetbuffer(ls->buff); + continue; + } + } + /* else short comment */ + while (!currIsNewline(ls) && ls->current != EOZ) + next(ls); + continue; + } + case '[': { + int sep = skip_sep(ls); + if (sep >= 0) { + read_long_string(ls, seminfo, sep); + return TK_STRING; + } + else if (sep == -1) return '['; + else luaX_lexerror(ls, "invalid long string delimiter", TK_STRING); + } + case '=': { + next(ls); + if (ls->current != '=') return '='; + else { next(ls); return TK_EQ; } + } + case '<': { + next(ls); + if (ls->current != '=') return '<'; + else { next(ls); return TK_LE; } + } + case '>': { + next(ls); + if (ls->current != '=') return '>'; + else { next(ls); return TK_GE; } + } + case '~': { + next(ls); + if (ls->current != '=') return '~'; + else { next(ls); return TK_NE; } + } + case '"': + case '\'': { + read_string(ls, ls->current, seminfo); + return TK_STRING; + } + case '.': { + save_and_next(ls); + if (check_next(ls, ".")) { + if (check_next(ls, ".")) + return TK_DOTS; /* ... */ + else return TK_CONCAT; /* .. */ + } + else if (!isdigit(ls->current)) return '.'; + else { + read_numeral(ls, seminfo); + return TK_NUMBER; + } + } + case EOZ: { + return TK_EOS; + } + default: { + if (isspace(ls->current)) { + lua_assert(!currIsNewline(ls)); + next(ls); + continue; + } + else if (isdigit(ls->current)) { + read_numeral(ls, seminfo); + return TK_NUMBER; + } + else if (isalpha(ls->current) || ls->current == '_') { + /* identifier or reserved word */ + TString *ts; + do { + save_and_next(ls); + } while (isalnum(ls->current) || ls->current == '_'); + ts = luaX_newstring(ls, luaZ_buffer(ls->buff), + luaZ_bufflen(ls->buff)); + if (ts->tsv.reserved > 0) /* reserved word? */ + return ts->tsv.reserved - 1 + FIRST_RESERVED; + else { + seminfo->ts = ts; + return TK_NAME; + } + } + else { + int c = ls->current; + next(ls); + return c; /* single-char tokens (+ - / ...) */ + } + } + } + } +} + + +void luaX_next (LexState *ls) { + ls->lastline = ls->linenumber; + if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */ + ls->t = ls->lookahead; /* use this one */ + ls->lookahead.token = TK_EOS; /* and discharge it */ + } + else + ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */ +} + + +void luaX_lookahead (LexState *ls) { + lua_assert(ls->lookahead.token == TK_EOS); + ls->lookahead.token = llex(ls, &ls->lookahead.seminfo); +} + diff --git a/engines/sword25/util/lua/llex.h b/engines/sword25/util/lua/llex.h new file mode 100644 index 0000000000..fa8b7a2a28 --- /dev/null +++ b/engines/sword25/util/lua/llex.h @@ -0,0 +1,81 @@ +/* +** $Id$ +** Lexical Analyzer +** See Copyright Notice in lua.h +*/ + +#ifndef llex_h +#define llex_h + +#include "lobject.h" +#include "lzio.h" + + +#define FIRST_RESERVED 257 + +/* maximum length of a reserved word */ +#define TOKEN_LEN (sizeof("function")/sizeof(char)) + + +/* +* WARNING: if you change the order of this enumeration, +* grep "ORDER RESERVED" +*/ +enum RESERVED { + /* terminal symbols denoted by reserved words */ + TK_AND = FIRST_RESERVED, TK_BREAK, + TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, + TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, + TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, + /* other terminal symbols */ + TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_NUMBER, + TK_NAME, TK_STRING, TK_EOS +}; + +/* number of reserved words */ +#define NUM_RESERVED (cast(int, TK_WHILE-FIRST_RESERVED+1)) + + +/* array with token `names' */ +LUAI_DATA const char *const luaX_tokens []; + + +typedef union { + lua_Number r; + TString *ts; +} SemInfo; /* semantics information */ + + +typedef struct Token { + int token; + SemInfo seminfo; +} Token; + + +typedef struct LexState { + int current; /* current character (charint) */ + int linenumber; /* input line counter */ + int lastline; /* line of last token `consumed' */ + Token t; /* current token */ + Token lookahead; /* look ahead token */ + struct FuncState *fs; /* `FuncState' is private to the parser */ + struct lua_State *L; + ZIO *z; /* input stream */ + Mbuffer *buff; /* buffer for tokens */ + TString *source; /* current source name */ + char decpoint; /* locale decimal point */ +} LexState; + + +LUAI_FUNC void luaX_init (lua_State *L); +LUAI_FUNC void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, + TString *source); +LUAI_FUNC TString *luaX_newstring (LexState *ls, const char *str, size_t l); +LUAI_FUNC void luaX_next (LexState *ls); +LUAI_FUNC void luaX_lookahead (LexState *ls); +LUAI_FUNC void luaX_lexerror (LexState *ls, const char *msg, int token); +LUAI_FUNC void luaX_syntaxerror (LexState *ls, const char *s); +LUAI_FUNC const char *luaX_token2str (LexState *ls, int token); + + +#endif diff --git a/engines/sword25/util/lua/llimits.h b/engines/sword25/util/lua/llimits.h new file mode 100644 index 0000000000..a31ad160ad --- /dev/null +++ b/engines/sword25/util/lua/llimits.h @@ -0,0 +1,128 @@ +/* +** $Id$ +** Limits, basic types, and some other `installation-dependent' definitions +** See Copyright Notice in lua.h +*/ + +#ifndef llimits_h +#define llimits_h + + +#include <limits.h> +#include <stddef.h> + + +#include "lua.h" + + +typedef LUAI_UINT32 lu_int32; + +typedef LUAI_UMEM lu_mem; + +typedef LUAI_MEM l_mem; + + + +/* chars used as small naturals (so that `char' is reserved for characters) */ +typedef unsigned char lu_byte; + + +#define MAX_SIZET ((size_t)(~(size_t)0)-2) + +#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)-2) + + +#define MAX_INT (INT_MAX-2) /* maximum value of an int (-2 for safety) */ + +/* +** conversion of pointer to integer +** this is for hashing only; there is no problem if the integer +** cannot hold the whole pointer value +*/ +#define IntPoint(p) ((unsigned int)(lu_mem)(p)) + + + +/* type to ensure maximum alignment */ +typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; + + +/* result of a `usual argument conversion' over lua_Number */ +typedef LUAI_UACNUMBER l_uacNumber; + + +/* internal assertions for in-house debugging */ +#ifdef lua_assert + +#define check_exp(c,e) (lua_assert(c), (e)) +#define api_check(l,e) lua_assert(e) + +#else + +#define lua_assert(c) ((void)0) +#define check_exp(c,e) (e) +#define api_check luai_apicheck + +#endif + + +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) /* to avoid warnings */ +#endif + + +#ifndef cast +#define cast(t, exp) ((t)(exp)) +#endif + +#define cast_byte(i) cast(lu_byte, (i)) +#define cast_num(i) cast(lua_Number, (i)) +#define cast_int(i) cast(int, (i)) + + + +/* +** type for virtual-machine instructions +** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) +*/ +typedef lu_int32 Instruction; + + + +/* maximum stack for a Lua function */ +#define MAXSTACK 250 + + + +/* minimum size for the string table (must be power of 2) */ +#ifndef MINSTRTABSIZE +#define MINSTRTABSIZE 32 +#endif + + +/* minimum size for string buffer */ +#ifndef LUA_MINBUFFER +#define LUA_MINBUFFER 32 +#endif + + +#ifndef lua_lock +#define lua_lock(L) ((void) 0) +#define lua_unlock(L) ((void) 0) +#endif + +#ifndef luai_threadyield +#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} +#endif + + +/* +** macro to control inclusion of some hard tests on stack reallocation +*/ +#ifndef HARDSTACKTESTS +#define condhardstacktests(x) ((void)0) +#else +#define condhardstacktests(x) x +#endif + +#endif diff --git a/engines/sword25/util/lua/lmathlib.cpp b/engines/sword25/util/lua/lmathlib.cpp new file mode 100644 index 0000000000..ed50539f1f --- /dev/null +++ b/engines/sword25/util/lua/lmathlib.cpp @@ -0,0 +1,273 @@ +/* +** $Id$ +** Standard mathematical library +** See Copyright Notice in lua.h +*/ + + +#include <stdlib.h> +#include <math.h> + +#define lmathlib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#undef PI +#define PI (3.14159265358979323846) +#define RADIANS_PER_DEGREE (PI/180.0) + + + +static int math_abs (lua_State *L) { + lua_pushnumber(L, fabs(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sin (lua_State *L) { + lua_pushnumber(L, sin(luaL_checknumber(L, 1))); + return 1; +} + +static int math_sinh (lua_State *L) { + lua_pushnumber(L, sinh(luaL_checknumber(L, 1))); + return 1; +} + +static int math_cos (lua_State *L) { + lua_pushnumber(L, cos(luaL_checknumber(L, 1))); + return 1; +} + +static int math_cosh (lua_State *L) { + lua_pushnumber(L, cosh(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tan (lua_State *L) { + lua_pushnumber(L, tan(luaL_checknumber(L, 1))); + return 1; +} + +static int math_tanh (lua_State *L) { + lua_pushnumber(L, tanh(luaL_checknumber(L, 1))); + return 1; +} + +static int math_asin (lua_State *L) { + lua_pushnumber(L, asin(luaL_checknumber(L, 1))); + return 1; +} + +static int math_acos (lua_State *L) { + lua_pushnumber(L, acos(luaL_checknumber(L, 1))); + return 1; +} + +static int math_atan (lua_State *L) { + lua_pushnumber(L, atan(luaL_checknumber(L, 1))); + return 1; +} + +static int math_atan2 (lua_State *L) { + lua_pushnumber(L, atan2(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); + return 1; +} + +static int math_ceil (lua_State *L) { + lua_pushnumber(L, ceil(luaL_checknumber(L, 1))); + return 1; +} + +static int math_floor (lua_State *L) { + lua_pushnumber(L, floor(luaL_checknumber(L, 1))); + return 1; +} + +static int math_fmod (lua_State *L) { + lua_pushnumber(L, fmod(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); + return 1; +} + +static int math_modf (lua_State *L) { + double ip; + double fp = modf(luaL_checknumber(L, 1), &ip); + lua_pushnumber(L, ip); + lua_pushnumber(L, fp); + return 2; +} + +static int math_sqrt (lua_State *L) { + lua_pushnumber(L, sqrt(luaL_checknumber(L, 1))); + return 1; +} + +static int math_pow (lua_State *L) { + lua_pushnumber(L, pow(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); + return 1; +} + +static int math_log (lua_State *L) { + lua_pushnumber(L, log(luaL_checknumber(L, 1))); + return 1; +} + +static int math_log10 (lua_State *L) { + lua_pushnumber(L, log10(luaL_checknumber(L, 1))); + return 1; +} + +static int math_exp (lua_State *L) { + lua_pushnumber(L, exp(luaL_checknumber(L, 1))); + return 1; +} + +static int math_deg (lua_State *L) { + lua_pushnumber(L, luaL_checknumber(L, 1)/RADIANS_PER_DEGREE); + return 1; +} + +static int math_rad (lua_State *L) { + lua_pushnumber(L, luaL_checknumber(L, 1)*RADIANS_PER_DEGREE); + return 1; +} + +static int math_frexp (lua_State *L) { + int e; + lua_pushnumber(L, frexp(luaL_checknumber(L, 1), &e)); + lua_pushinteger(L, e); + return 2; +} + +static int math_ldexp (lua_State *L) { + lua_pushnumber(L, ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2))); + return 1; +} + + + +static int math_min (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + lua_Number dmin = luaL_checknumber(L, 1); + int i; + for (i=2; i<=n; i++) { + lua_Number d = luaL_checknumber(L, i); + if (d < dmin) + dmin = d; + } + lua_pushnumber(L, dmin); + return 1; +} + + +static int math_max (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + lua_Number dmax = luaL_checknumber(L, 1); + int i; + for (i=2; i<=n; i++) { + lua_Number d = luaL_checknumber(L, i); + if (d > dmax) + dmax = d; + } + lua_pushnumber(L, dmax); + return 1; +} + + +static int math_random (lua_State *L) { + /* the `%' avoids the (rare) case of r==1, and is needed also because on + some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */ + lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX; + switch (lua_gettop(L)) { /* check number of arguments */ + case 0: { /* no arguments */ + lua_pushnumber(L, r); /* Number between 0 and 1 */ + break; + } + case 1: { /* only upper limit */ + int u = luaL_checkint(L, 1); + luaL_argcheck(L, 1<=u, 1, "interval is empty"); + lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */ + break; + } + case 2: { /* lower and upper limits */ + int l = luaL_checkint(L, 1); + int u = luaL_checkint(L, 2); + luaL_argcheck(L, l<=u, 2, "interval is empty"); + lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */ + break; + } + default: return luaL_error(L, "wrong number of arguments"); + } + return 1; +} + + +static int math_randomseed (lua_State *L) { + srand(luaL_checkint(L, 1)); + return 0; +} + + +static const luaL_Reg mathlib[] = { + {"abs", math_abs}, + {"acos", math_acos}, + {"asin", math_asin}, + {"atan2", math_atan2}, + {"atan", math_atan}, + {"ceil", math_ceil}, + {"cosh", math_cosh}, + {"cos", math_cos}, + {"deg", math_deg}, + {"exp", math_exp}, + {"floor", math_floor}, + {"fmod", math_fmod}, + {"frexp", math_frexp}, + {"ldexp", math_ldexp}, + {"log10", math_log10}, + {"log", math_log}, + {"max", math_max}, + {"min", math_min}, + {"modf", math_modf}, + {"pow", math_pow}, + {"rad", math_rad}, + {"random", math_random}, + {"randomseed", math_randomseed}, + {"sinh", math_sinh}, + {"sin", math_sin}, + {"sqrt", math_sqrt}, + {"tanh", math_tanh}, + {"tan", math_tan}, + {NULL, NULL} +}; + + +/* +** Open math library +*/ +LUALIB_API int luaopen_math (lua_State *L) { + luaL_register(L, LUA_MATHLIBNAME, mathlib); + lua_pushnumber(L, PI); + lua_setfield(L, -2, "pi"); +#if defined(MACOSX) && defined(__GNUC__) && ! defined( __XLC__ ) + // WORKAROUND for a bug in the Mac OS X 10.2.8 SDK. It defines + // HUGE_VAL simply to 1e500, leading to this compiler error: + // error: floating constant exceeds range of 'double' + // However, GCC (at least the version we are using for our cross + // compiler) has a __builtin_huge_val which returns the correct + // value, so we just use that. + lua_pushnumber(L, __builtin_huge_val()); +#else + lua_pushnumber(L, HUGE_VAL); +#endif + lua_setfield(L, -2, "huge"); +#if defined(LUA_COMPAT_MOD) + lua_getfield(L, -1, "fmod"); + lua_setfield(L, -2, "mod"); +#endif + return 1; +} + diff --git a/engines/sword25/util/lua/lmem.cpp b/engines/sword25/util/lua/lmem.cpp new file mode 100644 index 0000000000..ccd69357e0 --- /dev/null +++ b/engines/sword25/util/lua/lmem.cpp @@ -0,0 +1,86 @@ +/* +** $Id$ +** Interface to Memory Manager +** See Copyright Notice in lua.h +*/ + + +#include <stddef.h> + +#define lmem_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" + + + +/* +** About the realloc function: +** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize); +** (`osize' is the old size, `nsize' is the new size) +** +** Lua ensures that (ptr == NULL) iff (osize == 0). +** +** * frealloc(ud, NULL, 0, x) creates a new block of size `x' +** +** * frealloc(ud, p, x, 0) frees the block `p' +** (in this specific case, frealloc must return NULL). +** particularly, frealloc(ud, NULL, 0, 0) does nothing +** (which is equivalent to free(NULL) in ANSI C) +** +** frealloc returns NULL if it cannot create or reallocate the area +** (any reallocation to an equal or smaller size cannot fail!) +*/ + + + +#define MINSIZEARRAY 4 + + +void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elems, + int limit, const char *errormsg) { + void *newblock; + int newsize; + if (*size >= limit/2) { /* cannot double it? */ + if (*size >= limit) /* cannot grow even a little? */ + luaG_runerror(L, errormsg); + newsize = limit; /* still have at least one free place */ + } + else { + newsize = (*size)*2; + if (newsize < MINSIZEARRAY) + newsize = MINSIZEARRAY; /* minimum size */ + } + newblock = luaM_reallocv(L, block, *size, newsize, size_elems); + *size = newsize; /* update only when everything else is OK */ + return newblock; +} + + +void *luaM_toobig (lua_State *L) { + luaG_runerror(L, "memory allocation error: block too big"); + return NULL; /* to avoid warnings */ +} + + + +/* +** generic allocation routine. +*/ +void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { + global_State *g = G(L); + lua_assert((osize == 0) == (block == NULL)); + block = (*g->frealloc)(g->ud, block, osize, nsize); + if (block == NULL && nsize > 0) + luaD_throw(L, LUA_ERRMEM); + lua_assert((nsize == 0) == (block == NULL)); + g->totalbytes = (g->totalbytes - osize) + nsize; + return block; +} + diff --git a/engines/sword25/util/lua/lmem.h b/engines/sword25/util/lua/lmem.h new file mode 100644 index 0000000000..97a888c7f8 --- /dev/null +++ b/engines/sword25/util/lua/lmem.h @@ -0,0 +1,49 @@ +/* +** $Id$ +** Interface to Memory Manager +** See Copyright Notice in lua.h +*/ + +#ifndef lmem_h +#define lmem_h + + +#include <stddef.h> + +#include "llimits.h" +#include "lua.h" + +#define MEMERRMSG "not enough memory" + + +#define luaM_reallocv(L,b,on,n,e) \ + ((cast(size_t, (n)+1) <= MAX_SIZET/(e)) ? /* +1 to avoid warnings */ \ + luaM_realloc_(L, (b), (on)*(e), (n)*(e)) : \ + luaM_toobig(L)) + +#define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0) +#define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0) +#define luaM_freearray(L, b, n, t) luaM_reallocv(L, (b), n, 0, sizeof(t)) + +#define luaM_malloc(L,t) luaM_realloc_(L, NULL, 0, (t)) +#define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t))) +#define luaM_newvector(L,n,t) \ + cast(t *, luaM_reallocv(L, NULL, 0, n, sizeof(t))) + +#define luaM_growvector(L,v,nelems,size,t,limit,e) \ + if ((nelems)+1 > (size)) \ + ((v)=cast(t *, luaM_growaux_(L,v,&(size),sizeof(t),limit,e))) + +#define luaM_reallocvector(L, v,oldn,n,t) \ + ((v)=cast(t *, luaM_reallocv(L, v, oldn, n, sizeof(t)))) + + +LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize, + size_t size); +LUAI_FUNC void *luaM_toobig (lua_State *L); +LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int *size, + size_t size_elem, int limit, + const char *errormsg); + +#endif + diff --git a/engines/sword25/util/lua/loadlib.cpp b/engines/sword25/util/lua/loadlib.cpp new file mode 100644 index 0000000000..e060611450 --- /dev/null +++ b/engines/sword25/util/lua/loadlib.cpp @@ -0,0 +1,664 @@ +/* +** $Id$ +** Dynamic library loader for Lua +** See Copyright Notice in lua.h +** +** This module contains an implementation of loadlib for Unix systems +** that have dlfcn, an implementation for Darwin (Mac OS X), an +** implementation for Windows, and a stub for other systems. +*/ + + +#include <stdlib.h> +#include <string.h> + + +#define loadlib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* prefix for open functions in C libraries */ +#define LUA_POF "luaopen_" + +/* separator for open functions in C libraries */ +#define LUA_OFSEP "_" + + +#define LIBPREFIX "LOADLIB: " + +#define POF LUA_POF +#define LIB_FAIL "open" + + +/* error codes for ll_loadfunc */ +#define ERRLIB 1 +#define ERRFUNC 2 + +#define setprogdir(L) ((void)0) + + +static void ll_unloadlib (void *lib); +static void *ll_load (lua_State *L, const char *path); +static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym); + + + +#if defined(LUA_DL_DLOPEN) +/* +** {======================================================================== +** This is an implementation of loadlib based on the dlfcn interface. +** The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD, +** NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least +** as an emulation layer on top of native functions. +** ========================================================================= +*/ + +#include <dlfcn.h> + +static void ll_unloadlib (void *lib) { + dlclose(lib); +} + + +static void *ll_load (lua_State *L, const char *path) { + void *lib = dlopen(path, RTLD_NOW); + if (lib == NULL) lua_pushstring(L, dlerror()); + return lib; +} + + +static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = (lua_CFunction)dlsym(lib, sym); + if (f == NULL) lua_pushstring(L, dlerror()); + return f; +} + +/* }====================================================== */ + + + +#elif defined(LUA_DL_DLL) +/* +** {====================================================================== +** This is an implementation of loadlib for Windows using native functions. +** ======================================================================= +*/ + +#include <windows.h> + + +#undef setprogdir + +static void setprogdir (lua_State *L) { + char buff[MAX_PATH + 1]; + char *lb; + DWORD nsize = sizeof(buff)/sizeof(char); + DWORD n = GetModuleFileNameA(NULL, buff, nsize); + if (n == 0 || n == nsize || (lb = strrchr(buff, '\\')) == NULL) + luaL_error(L, "unable to get ModuleFileName"); + else { + *lb = '\0'; + luaL_gsub(L, lua_tostring(L, -1), LUA_EXECDIR, buff); + lua_remove(L, -2); /* remove original string */ + } +} + + +static void pusherror (lua_State *L) { + int error = GetLastError(); + char buffer[128]; + if (FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, error, 0, buffer, sizeof(buffer), NULL)) + lua_pushstring(L, buffer); + else + lua_pushfstring(L, "system error %d\n", error); +} + +static void ll_unloadlib (void *lib) { + FreeLibrary((HINSTANCE)lib); +} + + +static void *ll_load (lua_State *L, const char *path) { + HINSTANCE lib = LoadLibraryA(path); + if (lib == NULL) pusherror(L); + return lib; +} + + +static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { + lua_CFunction f = (lua_CFunction)GetProcAddress((HINSTANCE)lib, sym); + if (f == NULL) pusherror(L); + return f; +} + +/* }====================================================== */ + + + +#elif defined(LUA_DL_DYLD) +/* +** {====================================================================== +** Native Mac OS X / Darwin Implementation +** ======================================================================= +*/ + +#include <mach-o/dyld.h> + + +/* Mac appends a `_' before C function names */ +#undef POF +#define POF "_" LUA_POF + + +static void pusherror (lua_State *L) { + const char *err_str; + const char *err_file; + NSLinkEditErrors err; + int err_num; + NSLinkEditError(&err, &err_num, &err_file, &err_str); + lua_pushstring(L, err_str); +} + + +static const char *errorfromcode (NSObjectFileImageReturnCode ret) { + switch (ret) { + case NSObjectFileImageInappropriateFile: + return "file is not a bundle"; + case NSObjectFileImageArch: + return "library is for wrong CPU type"; + case NSObjectFileImageFormat: + return "bad format"; + case NSObjectFileImageAccess: + return "cannot access file"; + case NSObjectFileImageFailure: + default: + return "unable to load library"; + } +} + + +static void ll_unloadlib (void *lib) { + NSUnLinkModule((NSModule)lib, NSUNLINKMODULE_OPTION_RESET_LAZY_REFERENCES); +} + + +static void *ll_load (lua_State *L, const char *path) { + NSObjectFileImage img; + NSObjectFileImageReturnCode ret; + /* this would be a rare case, but prevents crashing if it happens */ + if(!_dyld_present()) { + lua_pushliteral(L, "dyld not present"); + return NULL; + } + ret = NSCreateObjectFileImageFromFile(path, &img); + if (ret == NSObjectFileImageSuccess) { + NSModule mod = NSLinkModule(img, path, NSLINKMODULE_OPTION_PRIVATE | + NSLINKMODULE_OPTION_RETURN_ON_ERROR); + NSDestroyObjectFileImage(img); + if (mod == NULL) pusherror(L); + return mod; + } + lua_pushstring(L, errorfromcode(ret)); + return NULL; +} + + +static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { + NSSymbol nss = NSLookupSymbolInModule((NSModule)lib, sym); + if (nss == NULL) { + lua_pushfstring(L, "symbol " LUA_QS " not found", sym); + return NULL; + } + return (lua_CFunction)NSAddressOfSymbol(nss); +} + +/* }====================================================== */ + + + +#else +/* +** {====================================================== +** Fallback for other systems +** ======================================================= +*/ + +#undef LIB_FAIL +#define LIB_FAIL "absent" + + +#define DLMSG "dynamic libraries not enabled; check your Lua installation" + + +static void ll_unloadlib (void *lib) { + (void)lib; /* to avoid warnings */ +} + + +static void *ll_load (lua_State *L, const char *path) { + (void)path; /* to avoid warnings */ + lua_pushliteral(L, DLMSG); + return NULL; +} + + +static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { + (void)lib; (void)sym; /* to avoid warnings */ + lua_pushliteral(L, DLMSG); + return NULL; +} + +/* }====================================================== */ +#endif + + + +static void **ll_register (lua_State *L, const char *path) { + void **plib; + lua_pushfstring(L, "%s%s", LIBPREFIX, path); + lua_gettable(L, LUA_REGISTRYINDEX); /* check library in registry? */ + if (!lua_isnil(L, -1)) /* is there an entry? */ + plib = (void **)lua_touserdata(L, -1); + else { /* no entry yet; create one */ + lua_pop(L, 1); + plib = (void **)lua_newuserdata(L, sizeof(const void *)); + *plib = NULL; + luaL_getmetatable(L, "_LOADLIB"); + lua_setmetatable(L, -2); + lua_pushfstring(L, "%s%s", LIBPREFIX, path); + lua_pushvalue(L, -2); + lua_settable(L, LUA_REGISTRYINDEX); + } + return plib; +} + + +/* +** __gc tag method: calls library's `ll_unloadlib' function with the lib +** handle +*/ +static int gctm (lua_State *L) { + void **lib = (void **)luaL_checkudata(L, 1, "_LOADLIB"); + if (*lib) ll_unloadlib(*lib); + *lib = NULL; /* mark library as closed */ + return 0; +} + + +static int ll_loadfunc (lua_State *L, const char *path, const char *sym) { + void **reg = ll_register(L, path); + if (*reg == NULL) *reg = ll_load(L, path); + if (*reg == NULL) + return ERRLIB; /* unable to load library */ + else { + lua_CFunction f = ll_sym(L, *reg, sym); + if (f == NULL) + return ERRFUNC; /* unable to find function */ + lua_pushcfunction(L, f); + return 0; /* return function */ + } +} + + +static int ll_loadlib (lua_State *L) { + const char *path = luaL_checkstring(L, 1); + const char *init = luaL_checkstring(L, 2); + int stat = ll_loadfunc(L, path, init); + if (stat == 0) /* no errors? */ + return 1; /* return the loaded function */ + else { /* error; error message is on stack top */ + lua_pushnil(L); + lua_insert(L, -2); + lua_pushstring(L, (stat == ERRLIB) ? LIB_FAIL : "init"); + return 3; /* return nil, error message, and where */ + } +} + + + +/* +** {====================================================== +** 'require' function +** ======================================================= +*/ + + +static int readable (const char *filename) { + FILE *f = fopen(filename, "r"); /* try to open file */ + if (f == NULL) return 0; /* open failed */ + fclose(f); + return 1; +} + + +static const char *pushnexttemplate (lua_State *L, const char *path) { + const char *l; + while (*path == *LUA_PATHSEP) path++; /* skip separators */ + if (*path == '\0') return NULL; /* no more templates */ + l = strchr(path, *LUA_PATHSEP); /* find next separator */ + if (l == NULL) l = path + strlen(path); + lua_pushlstring(L, path, l - path); /* template */ + return l; +} + + +static const char *findfile (lua_State *L, const char *name, + const char *pname) { + const char *path; + name = luaL_gsub(L, name, ".", LUA_DIRSEP); + lua_getfield(L, LUA_ENVIRONINDEX, pname); + path = lua_tostring(L, -1); + if (path == NULL) + luaL_error(L, LUA_QL("package.%s") " must be a string", pname); + lua_pushliteral(L, ""); /* error accumulator */ + while ((path = pushnexttemplate(L, path)) != NULL) { + const char *filename; + filename = luaL_gsub(L, lua_tostring(L, -1), LUA_PATH_MARK, name); + lua_remove(L, -2); /* remove path template */ + if (readable(filename)) /* does file exist and is readable? */ + return filename; /* return that file name */ + lua_pushfstring(L, "\n\tno file " LUA_QS, filename); + lua_remove(L, -2); /* remove file name */ + lua_concat(L, 2); /* add entry to possible error message */ + } + return NULL; /* not found */ +} + + +static void loaderror (lua_State *L, const char *filename) { + luaL_error(L, "error loading module " LUA_QS " from file " LUA_QS ":\n\t%s", + lua_tostring(L, 1), filename, lua_tostring(L, -1)); +} + + +static int loader_Lua (lua_State *L) { + const char *filename; + const char *name = luaL_checkstring(L, 1); + filename = findfile(L, name, "path"); + if (filename == NULL) return 1; /* library not found in this path */ + if (luaL_loadfile(L, filename) != 0) + loaderror(L, filename); + return 1; /* library loaded successfully */ +} + + +static const char *mkfuncname (lua_State *L, const char *modname) { + const char *funcname; + const char *mark = strchr(modname, *LUA_IGMARK); + if (mark) modname = mark + 1; + funcname = luaL_gsub(L, modname, ".", LUA_OFSEP); + funcname = lua_pushfstring(L, POF"%s", funcname); + lua_remove(L, -2); /* remove 'gsub' result */ + return funcname; +} + + +static int loader_C (lua_State *L) { + const char *funcname; + const char *name = luaL_checkstring(L, 1); + const char *filename = findfile(L, name, "cpath"); + if (filename == NULL) return 1; /* library not found in this path */ + funcname = mkfuncname(L, name); + if (ll_loadfunc(L, filename, funcname) != 0) + loaderror(L, filename); + return 1; /* library loaded successfully */ +} + + +static int loader_Croot (lua_State *L) { + const char *funcname; + const char *filename; + const char *name = luaL_checkstring(L, 1); + const char *p = strchr(name, '.'); + int stat; + if (p == NULL) return 0; /* is root */ + lua_pushlstring(L, name, p - name); + filename = findfile(L, lua_tostring(L, -1), "cpath"); + if (filename == NULL) return 1; /* root not found */ + funcname = mkfuncname(L, name); + if ((stat = ll_loadfunc(L, filename, funcname)) != 0) { + if (stat != ERRFUNC) loaderror(L, filename); /* real error */ + lua_pushfstring(L, "\n\tno module " LUA_QS " in file " LUA_QS, + name, filename); + return 1; /* function not found */ + } + return 1; +} + + +static int loader_preload (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + lua_getfield(L, LUA_ENVIRONINDEX, "preload"); + if (!lua_istable(L, -1)) + luaL_error(L, LUA_QL("package.preload") " must be a table"); + lua_getfield(L, -1, name); + if (lua_isnil(L, -1)) /* not found? */ + lua_pushfstring(L, "\n\tno field package.preload['%s']", name); + return 1; +} + + +static const int sentinel_ = 0; +#define sentinel ((void *)&sentinel_) + + +static int ll_require (lua_State *L) { + const char *name = luaL_checkstring(L, 1); + int i; + lua_settop(L, 1); /* _LOADED table will be at index 2 */ + lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); + lua_getfield(L, 2, name); + if (lua_toboolean(L, -1)) { /* is it there? */ + if (lua_touserdata(L, -1) == sentinel) /* check loops */ + luaL_error(L, "loop or previous error loading module " LUA_QS, name); + return 1; /* package is already loaded */ + } + /* else must load it; iterate over available loaders */ + lua_getfield(L, LUA_ENVIRONINDEX, "loaders"); + if (!lua_istable(L, -1)) + luaL_error(L, LUA_QL("package.loaders") " must be a table"); + lua_pushliteral(L, ""); /* error message accumulator */ + for (i=1; ; i++) { + lua_rawgeti(L, -2, i); /* get a loader */ + if (lua_isnil(L, -1)) + luaL_error(L, "module " LUA_QS " not found:%s", + name, lua_tostring(L, -2)); + lua_pushstring(L, name); + lua_call(L, 1, 1); /* call it */ + if (lua_isfunction(L, -1)) /* did it find module? */ + break; /* module loaded successfully */ + else if (lua_isstring(L, -1)) /* loader returned error message? */ + lua_concat(L, 2); /* accumulate it */ + else + lua_pop(L, 1); + } + lua_pushlightuserdata(L, sentinel); + lua_setfield(L, 2, name); /* _LOADED[name] = sentinel */ + lua_pushstring(L, name); /* pass name as argument to module */ + lua_call(L, 1, 1); /* run loaded module */ + if (!lua_isnil(L, -1)) /* non-nil return? */ + lua_setfield(L, 2, name); /* _LOADED[name] = returned value */ + lua_getfield(L, 2, name); + if (lua_touserdata(L, -1) == sentinel) { /* module did not set a value? */ + lua_pushboolean(L, 1); /* use true as result */ + lua_pushvalue(L, -1); /* extra copy to be returned */ + lua_setfield(L, 2, name); /* _LOADED[name] = true */ + } + return 1; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** 'module' function +** ======================================================= +*/ + + +static void setfenv (lua_State *L) { + lua_Debug ar; + lua_getstack(L, 1, &ar); + lua_getinfo(L, "f", &ar); + lua_pushvalue(L, -2); + lua_setfenv(L, -2); + lua_pop(L, 1); +} + + +static void dooptions (lua_State *L, int n) { + int i; + for (i = 2; i <= n; i++) { + lua_pushvalue(L, i); /* get option (a function) */ + lua_pushvalue(L, -2); /* module */ + lua_call(L, 1, 0); + } +} + + +static void modinit (lua_State *L, const char *modname) { + const char *dot; + lua_pushvalue(L, -1); + lua_setfield(L, -2, "_M"); /* module._M = module */ + lua_pushstring(L, modname); + lua_setfield(L, -2, "_NAME"); + dot = strrchr(modname, '.'); /* look for last dot in module name */ + if (dot == NULL) dot = modname; + else dot++; + /* set _PACKAGE as package name (full module name minus last part) */ + lua_pushlstring(L, modname, dot - modname); + lua_setfield(L, -2, "_PACKAGE"); +} + + +static int ll_module (lua_State *L) { + const char *modname = luaL_checkstring(L, 1); + int loaded = lua_gettop(L) + 1; /* index of _LOADED table */ + lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); + lua_getfield(L, loaded, modname); /* get _LOADED[modname] */ + if (!lua_istable(L, -1)) { /* not found? */ + lua_pop(L, 1); /* remove previous result */ + /* try global variable (and create one if it does not exist) */ + if (luaL_findtable(L, LUA_GLOBALSINDEX, modname, 1) != NULL) + return luaL_error(L, "name conflict for module " LUA_QS, modname); + lua_pushvalue(L, -1); + lua_setfield(L, loaded, modname); /* _LOADED[modname] = new table */ + } + /* check whether table already has a _NAME field */ + lua_getfield(L, -1, "_NAME"); + if (!lua_isnil(L, -1)) /* is table an initialized module? */ + lua_pop(L, 1); + else { /* no; initialize it */ + lua_pop(L, 1); + modinit(L, modname); + } + lua_pushvalue(L, -1); + setfenv(L); + dooptions(L, loaded - 1); + return 0; +} + + +static int ll_seeall (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + if (!lua_getmetatable(L, 1)) { + lua_createtable(L, 0, 1); /* create new metatable */ + lua_pushvalue(L, -1); + lua_setmetatable(L, 1); + } + lua_pushvalue(L, LUA_GLOBALSINDEX); + lua_setfield(L, -2, "__index"); /* mt.__index = _G */ + return 0; +} + + +/* }====================================================== */ + + + +/* auxiliary mark (for internal use) */ +#define AUXMARK "\1" + +static void setpath (lua_State *L, const char *fieldname, const char *envname, + const char *def) { + const char *path = getenv(envname); + if (path == NULL) /* no environment variable? */ + lua_pushstring(L, def); /* use default */ + else { + /* replace ";;" by ";AUXMARK;" and then AUXMARK by default path */ + path = luaL_gsub(L, path, LUA_PATHSEP LUA_PATHSEP, + LUA_PATHSEP AUXMARK LUA_PATHSEP); + luaL_gsub(L, path, AUXMARK, def); + lua_remove(L, -2); + } + setprogdir(L); + lua_setfield(L, -2, fieldname); +} + + +static const luaL_Reg pk_funcs[] = { + {"loadlib", ll_loadlib}, + {"seeall", ll_seeall}, + {NULL, NULL} +}; + + +static const luaL_Reg ll_funcs[] = { + {"module", ll_module}, + {"require", ll_require}, + {NULL, NULL} +}; + + +static const lua_CFunction loaders[] = + {loader_preload, loader_Lua, loader_C, loader_Croot, NULL}; + + +LUALIB_API int luaopen_package (lua_State *L) { + int i; + /* create new type _LOADLIB */ + luaL_newmetatable(L, "_LOADLIB"); + lua_pushcfunction(L, gctm); + lua_setfield(L, -2, "__gc"); + /* create `package' table */ + luaL_register(L, LUA_LOADLIBNAME, pk_funcs); +#if defined(LUA_COMPAT_LOADLIB) + lua_getfield(L, -1, "loadlib"); + lua_setfield(L, LUA_GLOBALSINDEX, "loadlib"); +#endif + lua_pushvalue(L, -1); + lua_replace(L, LUA_ENVIRONINDEX); + /* create `loaders' table */ + lua_createtable(L, 0, sizeof(loaders)/sizeof(loaders[0]) - 1); + /* fill it with pre-defined loaders */ + for (i=0; loaders[i] != NULL; i++) { + lua_pushcfunction(L, loaders[i]); + lua_rawseti(L, -2, i+1); + } + lua_setfield(L, -2, "loaders"); /* put it in field `loaders' */ + setpath(L, "path", LUA_PATH, LUA_PATH_DEFAULT); /* set field `path' */ + setpath(L, "cpath", LUA_CPATH, LUA_CPATH_DEFAULT); /* set field `cpath' */ + /* store config information */ + lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n" + LUA_EXECDIR "\n" LUA_IGMARK); + lua_setfield(L, -2, "config"); + /* set field `loaded' */ + luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 2); + lua_setfield(L, -2, "loaded"); + /* set field `preload' */ + lua_newtable(L); + lua_setfield(L, -2, "preload"); + lua_pushvalue(L, LUA_GLOBALSINDEX); + luaL_register(L, NULL, ll_funcs); /* open lib into global table */ + lua_pop(L, 1); + return 1; /* return 'package' table */ +} + diff --git a/engines/sword25/util/lua/lobject.cpp b/engines/sword25/util/lua/lobject.cpp new file mode 100644 index 0000000000..24718931ed --- /dev/null +++ b/engines/sword25/util/lua/lobject.cpp @@ -0,0 +1,214 @@ +/* +** $Id$ +** Some generic functions over Lua objects +** See Copyright Notice in lua.h +*/ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define lobject_c +#define LUA_CORE + +#include "lua.h" + +#include "ldo.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "lvm.h" + + + +const TValue luaO_nilobject_ = {{NULL}, LUA_TNIL}; + + +/* +** converts an integer to a "floating point byte", represented as +** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if +** eeeee != 0 and (xxx) otherwise. +*/ +int luaO_int2fb (unsigned int x) { + int e = 0; /* expoent */ + while (x >= 16) { + x = (x+1) >> 1; + e++; + } + if (x < 8) return x; + else return ((e+1) << 3) | (cast_int(x) - 8); +} + + +/* converts back */ +int luaO_fb2int (int x) { + int e = (x >> 3) & 31; + if (e == 0) return x; + else return ((x & 7)+8) << (e - 1); +} + + +int luaO_log2 (unsigned int x) { + static const lu_byte log_2[256] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 + }; + int l = -1; + while (x >= 256) { l += 8; x >>= 8; } + return l + log_2[x]; + +} + + +int luaO_rawequalObj (const TValue *t1, const TValue *t2) { + if (ttype(t1) != ttype(t2)) return 0; + else switch (ttype(t1)) { + case LUA_TNIL: + return 1; + case LUA_TNUMBER: + return luai_numeq(nvalue(t1), nvalue(t2)); + case LUA_TBOOLEAN: + return bvalue(t1) == bvalue(t2); /* boolean true must be 1 !! */ + case LUA_TLIGHTUSERDATA: + return pvalue(t1) == pvalue(t2); + default: + lua_assert(iscollectable(t1)); + return gcvalue(t1) == gcvalue(t2); + } +} + + +int luaO_str2d (const char *s, lua_Number *result) { + char *endptr; + *result = lua_str2number(s, &endptr); + if (endptr == s) return 0; /* conversion failed */ + if (*endptr == 'x' || *endptr == 'X') /* maybe an hexadecimal constant? */ + *result = cast_num(strtoul(s, &endptr, 16)); + if (*endptr == '\0') return 1; /* most common case */ + while (isspace(cast(unsigned char, *endptr))) endptr++; + if (*endptr != '\0') return 0; /* invalid trailing characters? */ + return 1; +} + + + +static void pushstr (lua_State *L, const char *str) { + setsvalue2s(L, L->top, luaS_new(L, str)); + incr_top(L); +} + + +/* this function handles only `%d', `%c', %f, %p, and `%s' formats */ +const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { + int n = 1; + pushstr(L, ""); + for (;;) { + const char *e = strchr(fmt, '%'); + if (e == NULL) break; + setsvalue2s(L, L->top, luaS_newlstr(L, fmt, e-fmt)); + incr_top(L); + switch (*(e+1)) { + case 's': { + const char *s = va_arg(argp, char *); + if (s == NULL) s = "(null)"; + pushstr(L, s); + break; + } + case 'c': { + char buff[2]; + buff[0] = cast(char, va_arg(argp, int)); + buff[1] = '\0'; + pushstr(L, buff); + break; + } + case 'd': { + setnvalue(L->top, cast_num(va_arg(argp, int))); + incr_top(L); + break; + } + case 'f': { + setnvalue(L->top, cast_num(va_arg(argp, l_uacNumber))); + incr_top(L); + break; + } + case 'p': { + char buff[4*sizeof(void *) + 8]; /* should be enough space for a `%p' */ + sprintf(buff, "%p", va_arg(argp, void *)); + pushstr(L, buff); + break; + } + case '%': { + pushstr(L, "%"); + break; + } + default: { + char buff[3]; + buff[0] = '%'; + buff[1] = *(e+1); + buff[2] = '\0'; + pushstr(L, buff); + break; + } + } + n += 2; + fmt = e+2; + } + pushstr(L, fmt); + luaV_concat(L, n+1, cast_int(L->top - L->base) - 1); + L->top -= n; + return svalue(L->top - 1); +} + + +const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) { + const char *msg; + va_list argp; + va_start(argp, fmt); + msg = luaO_pushvfstring(L, fmt, argp); + va_end(argp); + return msg; +} + + +void luaO_chunkid (char *out, const char *source, size_t bufflen) { + if (*source == '=') { + strncpy(out, source+1, bufflen); /* remove first char */ + out[bufflen-1] = '\0'; /* ensures null termination */ + } + else { /* out = "source", or "...source" */ + if (*source == '@') { + size_t l; + source++; /* skip the `@' */ + bufflen -= sizeof(" '...' "); + l = strlen(source); + strcpy(out, ""); + if (l > bufflen) { + source += (l-bufflen); /* get last part of file name */ + strcat(out, "..."); + } + strcat(out, source); + } + else { /* out = [string "string"] */ + size_t len = strcspn(source, "\n\r"); /* stop at first newline */ + bufflen -= sizeof(" [string \"...\"] "); + if (len > bufflen) len = bufflen; + strcpy(out, "[string \""); + if (source[len] != '\0') { /* must truncate? */ + strncat(out, source, len); + strcat(out, "..."); + } + else + strcat(out, source); + strcat(out, "\"]"); + } + } +} diff --git a/engines/sword25/util/lua/lobject.h b/engines/sword25/util/lua/lobject.h new file mode 100644 index 0000000000..35aaed028a --- /dev/null +++ b/engines/sword25/util/lua/lobject.h @@ -0,0 +1,381 @@ +/* +** $Id$ +** Type definitions for Lua objects +** See Copyright Notice in lua.h +*/ + + +#ifndef lobject_h +#define lobject_h + + +#include <stdarg.h> + + +#include "llimits.h" +#include "lua.h" + + +/* tags for values visible from Lua */ +#define LAST_TAG LUA_TTHREAD + +#define NUM_TAGS (LAST_TAG+1) + + +/* +** Extra tags for non-values +*/ +#define LUA_TPROTO (LAST_TAG+1) +#define LUA_TUPVAL (LAST_TAG+2) +#define LUA_TDEADKEY (LAST_TAG+3) + + +/* +** Union of all collectable objects +*/ +typedef union GCObject GCObject; + + +/* +** Common Header for all collectable objects (in macro form, to be +** included in other objects) +*/ +#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked + + +/* +** Common header in struct form +*/ +typedef struct GCheader { + CommonHeader; +} GCheader; + + + + +/* +** Union of all Lua values +*/ +typedef union { + GCObject *gc; + void *p; + lua_Number n; + int b; +} Value; + + +/* +** Tagged Values +*/ + +#define TValuefields Value value; int tt + +typedef struct lua_TValue { + TValuefields; +} TValue; + + +/* Macros to test type */ +#define ttisnil(o) (ttype(o) == LUA_TNIL) +#define ttisnumber(o) (ttype(o) == LUA_TNUMBER) +#define ttisstring(o) (ttype(o) == LUA_TSTRING) +#define ttistable(o) (ttype(o) == LUA_TTABLE) +#define ttisfunction(o) (ttype(o) == LUA_TFUNCTION) +#define ttisboolean(o) (ttype(o) == LUA_TBOOLEAN) +#define ttisuserdata(o) (ttype(o) == LUA_TUSERDATA) +#define ttisthread(o) (ttype(o) == LUA_TTHREAD) +#define ttislightuserdata(o) (ttype(o) == LUA_TLIGHTUSERDATA) + +/* Macros to access values */ +#define ttype(o) ((o)->tt) +#define gcvalue(o) check_exp(iscollectable(o), (o)->value.gc) +#define pvalue(o) check_exp(ttislightuserdata(o), (o)->value.p) +#define nvalue(o) check_exp(ttisnumber(o), (o)->value.n) +#define rawtsvalue(o) check_exp(ttisstring(o), &(o)->value.gc->ts) +#define tsvalue(o) (&rawtsvalue(o)->tsv) +#define rawuvalue(o) check_exp(ttisuserdata(o), &(o)->value.gc->u) +#define uvalue(o) (&rawuvalue(o)->uv) +#define clvalue(o) check_exp(ttisfunction(o), &(o)->value.gc->cl) +#define hvalue(o) check_exp(ttistable(o), &(o)->value.gc->h) +#define bvalue(o) check_exp(ttisboolean(o), (o)->value.b) +#define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th) + +#define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0)) + +/* +** for internal debug only +*/ +#define checkconsistency(obj) \ + lua_assert(!iscollectable(obj) || (ttype(obj) == (obj)->value.gc->gch.tt)) + +#define checkliveness(g,obj) \ + lua_assert(!iscollectable(obj) || \ + ((ttype(obj) == (obj)->value.gc->gch.tt) && !isdead(g, (obj)->value.gc))) + + +/* Macros to set values */ +#define setnilvalue(obj) ((obj)->tt=LUA_TNIL) + +#define setnvalue(obj,x) \ + { TValue *i_o=(obj); i_o->value.n=(x); i_o->tt=LUA_TNUMBER; } + +#define setpvalue(obj,x) \ + { TValue *i_o=(obj); i_o->value.p=(x); i_o->tt=LUA_TLIGHTUSERDATA; } + +#define setbvalue(obj,x) \ + { TValue *i_o=(obj); i_o->value.b=(x); i_o->tt=LUA_TBOOLEAN; } + +#define setsvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TSTRING; \ + checkliveness(G(L),i_o); } + +#define setuvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TUSERDATA; \ + checkliveness(G(L),i_o); } + +#define setthvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTHREAD; \ + checkliveness(G(L),i_o); } + +#define setclvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TFUNCTION; \ + checkliveness(G(L),i_o); } + +#define sethvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTABLE; \ + checkliveness(G(L),i_o); } + +#define setptvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TPROTO; \ + checkliveness(G(L),i_o); } + + + + +#define setobj(L,obj1,obj2) \ + { const TValue *o2=(obj2); TValue *o1=(obj1); \ + o1->value = o2->value; o1->tt=o2->tt; \ + checkliveness(G(L),o1); } + + +/* +** different types of sets, according to destination +*/ + +/* from stack to (same) stack */ +#define setobjs2s setobj +/* to stack (not from same stack) */ +#define setobj2s setobj +#define setsvalue2s setsvalue +#define sethvalue2s sethvalue +#define setptvalue2s setptvalue +/* from table to same table */ +#define setobjt2t setobj +/* to table */ +#define setobj2t setobj +/* to new object */ +#define setobj2n setobj +#define setsvalue2n setsvalue + +#define setttype(obj, tt) (ttype(obj) = (tt)) + + +#define iscollectable(o) (ttype(o) >= LUA_TSTRING) + + + +typedef TValue *StkId; /* index to stack elements */ + + +/* +** String headers for string table +*/ +typedef union TString { + L_Umaxalign dummy; /* ensures maximum alignment for strings */ + struct { + CommonHeader; + lu_byte reserved; + unsigned int hash; + size_t len; + } tsv; +} TString; + + +#define getstr(ts) cast(const char *, (ts) + 1) +#define svalue(o) getstr(tsvalue(o)) + + + +typedef union Udata { + L_Umaxalign dummy; /* ensures maximum alignment for `local' udata */ + struct { + CommonHeader; + struct Table *metatable; + struct Table *env; + size_t len; + } uv; +} Udata; + + + + +/* +** Function Prototypes +*/ +typedef struct Proto { + CommonHeader; + TValue *k; /* constants used by the function */ + Instruction *code; + struct Proto **p; /* functions defined inside the function */ + int *lineinfo; /* map from opcodes to source lines */ + struct LocVar *locvars; /* information about local variables */ + TString **upvalues; /* upvalue names */ + TString *source; + int sizeupvalues; + int sizek; /* size of `k' */ + int sizecode; + int sizelineinfo; + int sizep; /* size of `p' */ + int sizelocvars; + int linedefined; + int lastlinedefined; + GCObject *gclist; + lu_byte nups; /* number of upvalues */ + lu_byte numparams; + lu_byte is_vararg; + lu_byte maxstacksize; +} Proto; + + +/* masks for new-style vararg */ +#define VARARG_HASARG 1 +#define VARARG_ISVARARG 2 +#define VARARG_NEEDSARG 4 + + +typedef struct LocVar { + TString *varname; + int startpc; /* first point where variable is active */ + int endpc; /* first point where variable is dead */ +} LocVar; + + + +/* +** Upvalues +*/ + +typedef struct UpVal { + CommonHeader; + TValue *v; /* points to stack or to its own value */ + union { + TValue value; /* the value (when closed) */ + struct { /* double linked list (when open) */ + struct UpVal *prev; + struct UpVal *next; + } l; + } u; +} UpVal; + + +/* +** Closures +*/ + +#define ClosureHeader \ + CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \ + struct Table *env + +typedef struct CClosure { + ClosureHeader; + lua_CFunction f; + TValue upvalue[1]; +} CClosure; + + +typedef struct LClosure { + ClosureHeader; + struct Proto *p; + UpVal *upvals[1]; +} LClosure; + + +typedef union Closure { + CClosure c; + LClosure l; +} Closure; + + +#define iscfunction(o) (ttype(o) == LUA_TFUNCTION && clvalue(o)->c.isC) +#define isLfunction(o) (ttype(o) == LUA_TFUNCTION && !clvalue(o)->c.isC) + + +/* +** Tables +*/ + +typedef union TKey { + struct { + TValuefields; + struct Node *next; /* for chaining */ + } nk; + TValue tvk; +} TKey; + + +typedef struct Node { + TValue i_val; + TKey i_key; +} Node; + + +typedef struct Table { + CommonHeader; + lu_byte flags; /* 1<<p means tagmethod(p) is not present */ + lu_byte lsizenode; /* log2 of size of `node' array */ + struct Table *metatable; + TValue *array; /* array part */ + Node *node; + Node *lastfree; /* any free position is before this position */ + GCObject *gclist; + int sizearray; /* size of `array' array */ +} Table; + + + +/* +** `module' operation for hashing (size is always a power of 2) +*/ +#define lmod(s,size) \ + (check_exp((size&(size-1))==0, (cast(int, (s) & ((size)-1))))) + + +#define twoto(x) (1<<(x)) +#define sizenode(t) (twoto((t)->lsizenode)) + + +#define luaO_nilobject (&luaO_nilobject_) + +LUAI_DATA const TValue luaO_nilobject_; + +#define ceillog2(x) (luaO_log2((x)-1) + 1) + +LUAI_FUNC int luaO_log2 (unsigned int x); +LUAI_FUNC int luaO_int2fb (unsigned int x); +LUAI_FUNC int luaO_fb2int (int x); +LUAI_FUNC int luaO_rawequalObj (const TValue *t1, const TValue *t2); +LUAI_FUNC int luaO_str2d (const char *s, lua_Number *result); +LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt, + va_list argp); +LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...); +LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t len); + + +#endif + diff --git a/engines/sword25/util/lua/lopcodes.cpp b/engines/sword25/util/lua/lopcodes.cpp new file mode 100644 index 0000000000..d9da16f689 --- /dev/null +++ b/engines/sword25/util/lua/lopcodes.cpp @@ -0,0 +1,102 @@ +/* +** $Id$ +** See Copyright Notice in lua.h +*/ + + +#define lopcodes_c +#define LUA_CORE + + +#include "lopcodes.h" + + +/* ORDER OP */ + +const char *const luaP_opnames[NUM_OPCODES+1] = { + "MOVE", + "LOADK", + "LOADBOOL", + "LOADNIL", + "GETUPVAL", + "GETGLOBAL", + "GETTABLE", + "SETGLOBAL", + "SETUPVAL", + "SETTABLE", + "NEWTABLE", + "SELF", + "ADD", + "SUB", + "MUL", + "DIV", + "MOD", + "POW", + "UNM", + "NOT", + "LEN", + "CONCAT", + "JMP", + "EQ", + "LT", + "LE", + "TEST", + "TESTSET", + "CALL", + "TAILCALL", + "RETURN", + "FORLOOP", + "FORPREP", + "TFORLOOP", + "SETLIST", + "CLOSE", + "CLOSURE", + "VARARG", + NULL +}; + + +#define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m)) + +const lu_byte luaP_opmodes[NUM_OPCODES] = { +/* T A B C mode opcode */ + opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */ + ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_LOADBOOL */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LOADNIL */ + ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_GETUPVAL */ + ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_GETGLOBAL */ + ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_GETTABLE */ + ,opmode(0, 0, OpArgK, OpArgN, iABx) /* OP_SETGLOBAL */ + ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_SETUPVAL */ + ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABLE */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_NEWTABLE */ + ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_SELF */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */ + ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */ + ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */ + ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */ + ,opmode(0, 0, OpArgR, OpArgN, iAsBx) /* OP_JMP */ + ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_EQ */ + ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LT */ + ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LE */ + ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TEST */ + ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TESTSET */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_CALL */ + ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_TAILCALL */ + ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_RETURN */ + ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORLOOP */ + ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORPREP */ + ,opmode(1, 0, OpArgN, OpArgU, iABC) /* OP_TFORLOOP */ + ,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */ + ,opmode(0, 0, OpArgN, OpArgN, iABC) /* OP_CLOSE */ + ,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */ + ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */ +}; + diff --git a/engines/sword25/util/lua/lopcodes.h b/engines/sword25/util/lua/lopcodes.h new file mode 100644 index 0000000000..e1aed0f637 --- /dev/null +++ b/engines/sword25/util/lua/lopcodes.h @@ -0,0 +1,268 @@ +/* +** $Id$ +** Opcodes for Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#ifndef lopcodes_h +#define lopcodes_h + +#include "llimits.h" + + +/*=========================================================================== + We assume that instructions are unsigned numbers. + All instructions have an opcode in the first 6 bits. + Instructions can have the following fields: + `A' : 8 bits + `B' : 9 bits + `C' : 9 bits + `Bx' : 18 bits (`B' and `C' together) + `sBx' : signed Bx + + A signed argument is represented in excess K; that is, the number + value is the unsigned value minus K. K is exactly the maximum value + for that argument (so that -max is represented by 0, and +max is + represented by 2*max), which is half the maximum for the corresponding + unsigned argument. +===========================================================================*/ + + +enum OpMode {iABC, iABx, iAsBx}; /* basic instruction format */ + + +/* +** size and position of opcode arguments. +*/ +#define SIZE_C 9 +#define SIZE_B 9 +#define SIZE_Bx (SIZE_C + SIZE_B) +#define SIZE_A 8 + +#define SIZE_OP 6 + +#define POS_OP 0 +#define POS_A (POS_OP + SIZE_OP) +#define POS_C (POS_A + SIZE_A) +#define POS_B (POS_C + SIZE_C) +#define POS_Bx POS_C + + +/* +** limits for opcode arguments. +** we use (signed) int to manipulate most arguments, +** so they must fit in LUAI_BITSINT-1 bits (-1 for sign) +*/ +#if SIZE_Bx < LUAI_BITSINT-1 +#define MAXARG_Bx ((1<<SIZE_Bx)-1) +#define MAXARG_sBx (MAXARG_Bx>>1) /* `sBx' is signed */ +#else +#define MAXARG_Bx MAX_INT +#define MAXARG_sBx MAX_INT +#endif + + +#define MAXARG_A ((1<<SIZE_A)-1) +#define MAXARG_B ((1<<SIZE_B)-1) +#define MAXARG_C ((1<<SIZE_C)-1) + + +/* creates a mask with `n' 1 bits at position `p' */ +#define MASK1(n,p) ((~((~(Instruction)0)<<n))<<p) + +/* creates a mask with `n' 0 bits at position `p' */ +#define MASK0(n,p) (~MASK1(n,p)) + +/* +** the following macros help to manipulate instructions +*/ + +#define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0))) +#define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ + ((cast(Instruction, o)<<POS_OP)&MASK1(SIZE_OP,POS_OP)))) + +#define GETARG_A(i) (cast(int, ((i)>>POS_A) & MASK1(SIZE_A,0))) +#define SETARG_A(i,u) ((i) = (((i)&MASK0(SIZE_A,POS_A)) | \ + ((cast(Instruction, u)<<POS_A)&MASK1(SIZE_A,POS_A)))) + +#define GETARG_B(i) (cast(int, ((i)>>POS_B) & MASK1(SIZE_B,0))) +#define SETARG_B(i,b) ((i) = (((i)&MASK0(SIZE_B,POS_B)) | \ + ((cast(Instruction, b)<<POS_B)&MASK1(SIZE_B,POS_B)))) + +#define GETARG_C(i) (cast(int, ((i)>>POS_C) & MASK1(SIZE_C,0))) +#define SETARG_C(i,b) ((i) = (((i)&MASK0(SIZE_C,POS_C)) | \ + ((cast(Instruction, b)<<POS_C)&MASK1(SIZE_C,POS_C)))) + +#define GETARG_Bx(i) (cast(int, ((i)>>POS_Bx) & MASK1(SIZE_Bx,0))) +#define SETARG_Bx(i,b) ((i) = (((i)&MASK0(SIZE_Bx,POS_Bx)) | \ + ((cast(Instruction, b)<<POS_Bx)&MASK1(SIZE_Bx,POS_Bx)))) + +#define GETARG_sBx(i) (GETARG_Bx(i)-MAXARG_sBx) +#define SETARG_sBx(i,b) SETARG_Bx((i),cast(unsigned int, (b)+MAXARG_sBx)) + + +#define CREATE_ABC(o,a,b,c) ((cast(Instruction, o)<<POS_OP) \ + | (cast(Instruction, a)<<POS_A) \ + | (cast(Instruction, b)<<POS_B) \ + | (cast(Instruction, c)<<POS_C)) + +#define CREATE_ABx(o,a,bc) ((cast(Instruction, o)<<POS_OP) \ + | (cast(Instruction, a)<<POS_A) \ + | (cast(Instruction, bc)<<POS_Bx)) + + +/* +** Macros to operate RK indices +*/ + +/* this bit 1 means constant (0 means register) */ +#define BITRK (1 << (SIZE_B - 1)) + +/* test whether value is a constant */ +#define ISK(x) ((x) & BITRK) + +/* gets the index of the constant */ +#define INDEXK(r) ((int)(r) & ~BITRK) + +#define MAXINDEXRK (BITRK - 1) + +/* code a constant index as a RK value */ +#define RKASK(x) ((x) | BITRK) + + +/* +** invalid register that fits in 8 bits +*/ +#define NO_REG MAXARG_A + + +/* +** R(x) - register +** Kst(x) - constant (in constant table) +** RK(x) == if ISK(x) then Kst(INDEXK(x)) else R(x) +*/ + + +/* +** grep "ORDER OP" if you change these enums +*/ + +typedef enum { +/*---------------------------------------------------------------------- +name args description +------------------------------------------------------------------------*/ +OP_MOVE,/* A B R(A) := R(B) */ +OP_LOADK,/* A Bx R(A) := Kst(Bx) */ +OP_LOADBOOL,/* A B C R(A) := (Bool)B; if (C) pc++ */ +OP_LOADNIL,/* A B R(A) := ... := R(B) := nil */ +OP_GETUPVAL,/* A B R(A) := UpValue[B] */ + +OP_GETGLOBAL,/* A Bx R(A) := Gbl[Kst(Bx)] */ +OP_GETTABLE,/* A B C R(A) := R(B)[RK(C)] */ + +OP_SETGLOBAL,/* A Bx Gbl[Kst(Bx)] := R(A) */ +OP_SETUPVAL,/* A B UpValue[B] := R(A) */ +OP_SETTABLE,/* A B C R(A)[RK(B)] := RK(C) */ + +OP_NEWTABLE,/* A B C R(A) := {} (size = B,C) */ + +OP_SELF,/* A B C R(A+1) := R(B); R(A) := R(B)[RK(C)] */ + +OP_ADD,/* A B C R(A) := RK(B) + RK(C) */ +OP_SUB,/* A B C R(A) := RK(B) - RK(C) */ +OP_MUL,/* A B C R(A) := RK(B) * RK(C) */ +OP_DIV,/* A B C R(A) := RK(B) / RK(C) */ +OP_MOD,/* A B C R(A) := RK(B) % RK(C) */ +OP_POW,/* A B C R(A) := RK(B) ^ RK(C) */ +OP_UNM,/* A B R(A) := -R(B) */ +OP_NOT,/* A B R(A) := not R(B) */ +OP_LEN,/* A B R(A) := length of R(B) */ + +OP_CONCAT,/* A B C R(A) := R(B).. ... ..R(C) */ + +OP_JMP,/* sBx pc+=sBx */ + +OP_EQ,/* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ +OP_LT,/* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ +OP_LE,/* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ + +OP_TEST,/* A C if not (R(A) <=> C) then pc++ */ +OP_TESTSET,/* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ + +OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */ +OP_TAILCALL,/* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ +OP_RETURN,/* A B return R(A), ... ,R(A+B-2) (see note) */ + +OP_FORLOOP,/* A sBx R(A)+=R(A+2); + if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/ +OP_FORPREP,/* A sBx R(A)-=R(A+2); pc+=sBx */ + +OP_TFORLOOP,/* A C R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2)); + if R(A+3) ~= nil then R(A+2)=R(A+3) else pc++ */ +OP_SETLIST,/* A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B */ + +OP_CLOSE,/* A close all variables in the stack up to (>=) R(A)*/ +OP_CLOSURE,/* A Bx R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n)) */ + +OP_VARARG/* A B R(A), R(A+1), ..., R(A+B-1) = vararg */ +} OpCode; + + +#define NUM_OPCODES (cast(int, OP_VARARG) + 1) + + + +/*=========================================================================== + Notes: + (*) In OP_CALL, if (B == 0) then B = top. C is the number of returns - 1, + and can be 0: OP_CALL then sets `top' to last_result+1, so + next open instruction (OP_CALL, OP_RETURN, OP_SETLIST) may use `top'. + + (*) In OP_VARARG, if (B == 0) then use actual number of varargs and + set top (like in OP_CALL with C == 0). + + (*) In OP_RETURN, if (B == 0) then return up to `top' + + (*) In OP_SETLIST, if (B == 0) then B = `top'; + if (C == 0) then next `instruction' is real C + + (*) For comparisons, A specifies what condition the test should accept + (true or false). + + (*) All `skips' (pc++) assume that next instruction is a jump +===========================================================================*/ + + +/* +** masks for instruction properties. The format is: +** bits 0-1: op mode +** bits 2-3: C arg mode +** bits 4-5: B arg mode +** bit 6: instruction set register A +** bit 7: operator is a test +*/ + +enum OpArgMask { + OpArgN, /* argument is not used */ + OpArgU, /* argument is used */ + OpArgR, /* argument is a register or a jump offset */ + OpArgK /* argument is a constant or register/constant */ +}; + +LUAI_DATA const lu_byte luaP_opmodes[NUM_OPCODES]; + +#define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 3)) +#define getBMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 4) & 3)) +#define getCMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 2) & 3)) +#define testAMode(m) (luaP_opmodes[m] & (1 << 6)) +#define testTMode(m) (luaP_opmodes[m] & (1 << 7)) + + +LUAI_DATA const char *const luaP_opnames[NUM_OPCODES+1]; /* opcode names */ + + +/* number of list items to accumulate before a SETLIST instruction */ +#define LFIELDS_PER_FLUSH 50 + + +#endif diff --git a/engines/sword25/util/lua/loslib.cpp b/engines/sword25/util/lua/loslib.cpp new file mode 100644 index 0000000000..70a67bccf7 --- /dev/null +++ b/engines/sword25/util/lua/loslib.cpp @@ -0,0 +1,243 @@ +/* +** $Id$ +** Standard Operating System library +** See Copyright Notice in lua.h +*/ + + +#include <errno.h> +#include <locale.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#define loslib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +static int os_pushresult (lua_State *L, int i, const char *filename) { + int en = errno; /* calls to Lua API may change this value */ + if (i) { + lua_pushboolean(L, 1); + return 1; + } + else { + lua_pushnil(L); + lua_pushfstring(L, "%s: %s", filename, strerror(en)); + lua_pushinteger(L, en); + return 3; + } +} + + +static int os_execute (lua_State *L) { + lua_pushinteger(L, system(luaL_optstring(L, 1, NULL))); + return 1; +} + + +static int os_remove (lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + return os_pushresult(L, remove(filename) == 0, filename); +} + + +static int os_rename (lua_State *L) { + const char *fromname = luaL_checkstring(L, 1); + const char *toname = luaL_checkstring(L, 2); + return os_pushresult(L, rename(fromname, toname) == 0, fromname); +} + + +static int os_tmpname (lua_State *L) { + char buff[LUA_TMPNAMBUFSIZE]; + int err; + lua_tmpnam(buff, err); + if (err) + return luaL_error(L, "unable to generate a unique filename"); + lua_pushstring(L, buff); + return 1; +} + + +static int os_getenv (lua_State *L) { + lua_pushstring(L, getenv(luaL_checkstring(L, 1))); /* if NULL push nil */ + return 1; +} + + +static int os_clock (lua_State *L) { + lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC); + return 1; +} + + +/* +** {====================================================== +** Time/Date operations +** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S, +** wday=%w+1, yday=%j, isdst=? } +** ======================================================= +*/ + +static void setfield (lua_State *L, const char *key, int value) { + lua_pushinteger(L, value); + lua_setfield(L, -2, key); +} + +static void setboolfield (lua_State *L, const char *key, int value) { + if (value < 0) /* undefined? */ + return; /* does not set field */ + lua_pushboolean(L, value); + lua_setfield(L, -2, key); +} + +static int getboolfield (lua_State *L, const char *key) { + int res; + lua_getfield(L, -1, key); + res = lua_isnil(L, -1) ? -1 : lua_toboolean(L, -1); + lua_pop(L, 1); + return res; +} + + +static int getfield (lua_State *L, const char *key, int d) { + int res; + lua_getfield(L, -1, key); + if (lua_isnumber(L, -1)) + res = (int)lua_tointeger(L, -1); + else { + if (d < 0) + return luaL_error(L, "field " LUA_QS " missing in date table", key); + res = d; + } + lua_pop(L, 1); + return res; +} + + +static int os_date (lua_State *L) { + const char *s = luaL_optstring(L, 1, "%c"); + time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL)); + struct tm *stm; + if (*s == '!') { /* UTC? */ + stm = gmtime(&t); + s++; /* skip `!' */ + } + else + stm = localtime(&t); + if (stm == NULL) /* invalid date? */ + lua_pushnil(L); + else if (strcmp(s, "*t") == 0) { + lua_createtable(L, 0, 9); /* 9 = number of fields */ + setfield(L, "sec", stm->tm_sec); + setfield(L, "min", stm->tm_min); + setfield(L, "hour", stm->tm_hour); + setfield(L, "day", stm->tm_mday); + setfield(L, "month", stm->tm_mon+1); + setfield(L, "year", stm->tm_year+1900); + setfield(L, "wday", stm->tm_wday+1); + setfield(L, "yday", stm->tm_yday+1); + setboolfield(L, "isdst", stm->tm_isdst); + } + else { + char cc[3]; + luaL_Buffer b; + cc[0] = '%'; cc[2] = '\0'; + luaL_buffinit(L, &b); + for (; *s; s++) { + if (*s != '%' || *(s + 1) == '\0') /* no conversion specifier? */ + luaL_addchar(&b, *s); + else { + size_t reslen; + char buff[200]; /* should be big enough for any conversion result */ + cc[1] = *(++s); + reslen = strftime(buff, sizeof(buff), cc, stm); + luaL_addlstring(&b, buff, reslen); + } + } + luaL_pushresult(&b); + } + return 1; +} + + +static int os_time (lua_State *L) { + time_t t; + if (lua_isnoneornil(L, 1)) /* called without args? */ + t = time(NULL); /* get current time */ + else { + struct tm ts; + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 1); /* make sure table is at the top */ + ts.tm_sec = getfield(L, "sec", 0); + ts.tm_min = getfield(L, "min", 0); + ts.tm_hour = getfield(L, "hour", 12); + ts.tm_mday = getfield(L, "day", -1); + ts.tm_mon = getfield(L, "month", -1) - 1; + ts.tm_year = getfield(L, "year", -1) - 1900; + ts.tm_isdst = getboolfield(L, "isdst"); + t = mktime(&ts); + } + if (t == (time_t)(-1)) + lua_pushnil(L); + else + lua_pushnumber(L, (lua_Number)t); + return 1; +} + + +static int os_difftime (lua_State *L) { + lua_pushnumber(L, difftime((time_t)(luaL_checknumber(L, 1)), + (time_t)(luaL_optnumber(L, 2, 0)))); + return 1; +} + +/* }====================================================== */ + + +static int os_setlocale (lua_State *L) { + static const int cat[] = {LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, + LC_NUMERIC, LC_TIME}; + static const char *const catnames[] = {"all", "collate", "ctype", "monetary", + "numeric", "time", NULL}; + const char *l = luaL_optstring(L, 1, NULL); + int op = luaL_checkoption(L, 2, "all", catnames); + lua_pushstring(L, setlocale(cat[op], l)); + return 1; +} + + +static int os_exit (lua_State *L) { + exit(luaL_optint(L, 1, EXIT_SUCCESS)); +} + +static const luaL_Reg syslib[] = { + {"clock", os_clock}, + {"date", os_date}, + {"difftime", os_difftime}, + {"execute", os_execute}, + {"exit", os_exit}, + {"getenv", os_getenv}, + {"remove", os_remove}, + {"rename", os_rename}, + {"setlocale", os_setlocale}, + {"time", os_time}, + {"tmpname", os_tmpname}, + {NULL, NULL} +}; + +/* }====================================================== */ + + + +LUALIB_API int luaopen_os (lua_State *L) { + luaL_register(L, LUA_OSLIBNAME, syslib); + return 1; +} + diff --git a/engines/sword25/util/lua/lparser.cpp b/engines/sword25/util/lua/lparser.cpp new file mode 100644 index 0000000000..03ea333315 --- /dev/null +++ b/engines/sword25/util/lua/lparser.cpp @@ -0,0 +1,1339 @@ +/* +** $Id$ +** Lua Parser +** See Copyright Notice in lua.h +*/ + + +#include <string.h> + +#define lparser_c +#define LUA_CORE + +#include "lua.h" + +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "llex.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lparser.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" + + + +#define hasmultret(k) ((k) == VCALL || (k) == VVARARG) + +#define getlocvar(fs, i) ((fs)->f->locvars[(fs)->actvar[i]]) + +#define luaY_checklimit(fs,v,l,m) if ((v)>(l)) errorlimit(fs,l,m) + + +/* +** nodes for block list (list of active blocks) +*/ +typedef struct BlockCnt { + struct BlockCnt *previous; /* chain */ + int breaklist; /* list of jumps out of this loop */ + lu_byte nactvar; /* # active locals outside the breakable structure */ + lu_byte upval; /* true if some variable in the block is an upvalue */ + lu_byte isbreakable; /* true if `block' is a loop */ +} BlockCnt; + + + +/* +** prototypes for recursive non-terminal functions +*/ +static void chunk (LexState *ls); +static void expr (LexState *ls, expdesc *v); + + +static void anchor_token (LexState *ls) { + if (ls->t.token == TK_NAME || ls->t.token == TK_STRING) { + TString *ts = ls->t.seminfo.ts; + luaX_newstring(ls, getstr(ts), ts->tsv.len); + } +} + + +static void error_expected (LexState *ls, int token) { + luaX_syntaxerror(ls, + luaO_pushfstring(ls->L, LUA_QS " expected", luaX_token2str(ls, token))); +} + + +static void errorlimit (FuncState *fs, int limit, const char *what) { + const char *msg = (fs->f->linedefined == 0) ? + luaO_pushfstring(fs->L, "main function has more than %d %s", limit, what) : + luaO_pushfstring(fs->L, "function at line %d has more than %d %s", + fs->f->linedefined, limit, what); + luaX_lexerror(fs->ls, msg, 0); +} + + +static int testnext (LexState *ls, int c) { + if (ls->t.token == c) { + luaX_next(ls); + return 1; + } + else return 0; +} + + +static void check (LexState *ls, int c) { + if (ls->t.token != c) + error_expected(ls, c); +} + +static void checknext (LexState *ls, int c) { + check(ls, c); + luaX_next(ls); +} + + +#define check_condition(ls,c,msg) { if (!(c)) luaX_syntaxerror(ls, msg); } + + + +static void check_match (LexState *ls, int what, int who, int where) { + if (!testnext(ls, what)) { + if (where == ls->linenumber) + error_expected(ls, what); + else { + luaX_syntaxerror(ls, luaO_pushfstring(ls->L, + LUA_QS " expected (to close " LUA_QS " at line %d)", + luaX_token2str(ls, what), luaX_token2str(ls, who), where)); + } + } +} + + +static TString *str_checkname (LexState *ls) { + TString *ts; + check(ls, TK_NAME); + ts = ls->t.seminfo.ts; + luaX_next(ls); + return ts; +} + + +static void init_exp (expdesc *e, expkind k, int i) { + e->f = e->t = NO_JUMP; + e->k = k; + e->u.s.info = i; +} + + +static void codestring (LexState *ls, expdesc *e, TString *s) { + init_exp(e, VK, luaK_stringK(ls->fs, s)); +} + + +static void checkname(LexState *ls, expdesc *e) { + codestring(ls, e, str_checkname(ls)); +} + + +static int registerlocalvar (LexState *ls, TString *varname) { + FuncState *fs = ls->fs; + Proto *f = fs->f; + int oldsize = f->sizelocvars; + luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars, + LocVar, SHRT_MAX, "too many local variables"); + while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL; + f->locvars[fs->nlocvars].varname = varname; + luaC_objbarrier(ls->L, f, varname); + return fs->nlocvars++; +} + + +#define new_localvarliteral(ls,v,n) \ + new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char))-1), n) + + +static void new_localvar (LexState *ls, TString *name, int n) { + FuncState *fs = ls->fs; + luaY_checklimit(fs, fs->nactvar+n+1, LUAI_MAXVARS, "local variables"); + fs->actvar[fs->nactvar+n] = cast(unsigned short, registerlocalvar(ls, name)); +} + + +static void adjustlocalvars (LexState *ls, int nvars) { + FuncState *fs = ls->fs; + fs->nactvar = cast_byte(fs->nactvar + nvars); + for (; nvars; nvars--) { + getlocvar(fs, fs->nactvar - nvars).startpc = fs->pc; + } +} + + +static void removevars (LexState *ls, int tolevel) { + FuncState *fs = ls->fs; + while (fs->nactvar > tolevel) + getlocvar(fs, --fs->nactvar).endpc = fs->pc; +} + + +static int indexupvalue (FuncState *fs, TString *name, expdesc *v) { + int i; + Proto *f = fs->f; + int oldsize = f->sizeupvalues; + for (i=0; i<f->nups; i++) { + if (fs->upvalues[i].k == v->k && fs->upvalues[i].info == v->u.s.info) { + lua_assert(f->upvalues[i] == name); + return i; + } + } + /* new one */ + luaY_checklimit(fs, f->nups + 1, LUAI_MAXUPVALUES, "upvalues"); + luaM_growvector(fs->L, f->upvalues, f->nups, f->sizeupvalues, + TString *, MAX_INT, ""); + while (oldsize < f->sizeupvalues) f->upvalues[oldsize++] = NULL; + f->upvalues[f->nups] = name; + luaC_objbarrier(fs->L, f, name); + lua_assert(v->k == VLOCAL || v->k == VUPVAL); + fs->upvalues[f->nups].k = cast_byte(v->k); + fs->upvalues[f->nups].info = cast_byte(v->u.s.info); + return f->nups++; +} + + +static int searchvar (FuncState *fs, TString *n) { + int i; + for (i=fs->nactvar-1; i >= 0; i--) { + if (n == getlocvar(fs, i).varname) + return i; + } + return -1; /* not found */ +} + + +static void markupval (FuncState *fs, int level) { + BlockCnt *bl = fs->bl; + while (bl && bl->nactvar > level) bl = bl->previous; + if (bl) bl->upval = 1; +} + + +static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { + if (fs == NULL) { /* no more levels? */ + init_exp(var, VGLOBAL, NO_REG); /* default is global variable */ + return VGLOBAL; + } + else { + int v = searchvar(fs, n); /* look up at current level */ + if (v >= 0) { + init_exp(var, VLOCAL, v); + if (!base) + markupval(fs, v); /* local will be used as an upval */ + return VLOCAL; + } + else { /* not found at current level; try upper one */ + if (singlevaraux(fs->prev, n, var, 0) == VGLOBAL) + return VGLOBAL; + var->u.s.info = indexupvalue(fs, n, var); /* else was LOCAL or UPVAL */ + var->k = VUPVAL; /* upvalue in this level */ + return VUPVAL; + } + } +} + + +static void singlevar (LexState *ls, expdesc *var) { + TString *varname = str_checkname(ls); + FuncState *fs = ls->fs; + if (singlevaraux(fs, varname, var, 1) == VGLOBAL) + var->u.s.info = luaK_stringK(fs, varname); /* info points to global name */ +} + + +static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { + FuncState *fs = ls->fs; + int extra = nvars - nexps; + if (hasmultret(e->k)) { + extra++; /* includes call itself */ + if (extra < 0) extra = 0; + luaK_setreturns(fs, e, extra); /* last exp. provides the difference */ + if (extra > 1) luaK_reserveregs(fs, extra-1); + } + else { + if (e->k != VVOID) luaK_exp2nextreg(fs, e); /* close last expression */ + if (extra > 0) { + int reg = fs->freereg; + luaK_reserveregs(fs, extra); + luaK_nil(fs, reg, extra); + } + } +} + + +static void enterlevel (LexState *ls) { + if (++ls->L->nCcalls > LUAI_MAXCCALLS) + luaX_lexerror(ls, "chunk has too many syntax levels", 0); +} + + +#define leavelevel(ls) ((ls)->L->nCcalls--) + + +static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isbreakable) { + bl->breaklist = NO_JUMP; + bl->isbreakable = isbreakable; + bl->nactvar = fs->nactvar; + bl->upval = 0; + bl->previous = fs->bl; + fs->bl = bl; + lua_assert(fs->freereg == fs->nactvar); +} + + +static void leaveblock (FuncState *fs) { + BlockCnt *bl = fs->bl; + fs->bl = bl->previous; + removevars(fs->ls, bl->nactvar); + if (bl->upval) + luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0); + /* a block either controls scope or breaks (never both) */ + lua_assert(!bl->isbreakable || !bl->upval); + lua_assert(bl->nactvar == fs->nactvar); + fs->freereg = fs->nactvar; /* free registers */ + luaK_patchtohere(fs, bl->breaklist); +} + + +static void pushclosure (LexState *ls, FuncState *func, expdesc *v) { + FuncState *fs = ls->fs; + Proto *f = fs->f; + int oldsize = f->sizep; + int i; + luaM_growvector(ls->L, f->p, fs->np, f->sizep, Proto *, + MAXARG_Bx, "constant table overflow"); + while (oldsize < f->sizep) f->p[oldsize++] = NULL; + f->p[fs->np++] = func->f; + luaC_objbarrier(ls->L, f, func->f); + init_exp(v, VRELOCABLE, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np-1)); + for (i=0; i<func->f->nups; i++) { + OpCode o = (func->upvalues[i].k == VLOCAL) ? OP_MOVE : OP_GETUPVAL; + luaK_codeABC(fs, o, 0, func->upvalues[i].info, 0); + } +} + + +static void open_func (LexState *ls, FuncState *fs) { + lua_State *L = ls->L; + Proto *f = luaF_newproto(L); + fs->f = f; + fs->prev = ls->fs; /* linked list of funcstates */ + fs->ls = ls; + fs->L = L; + ls->fs = fs; + fs->pc = 0; + fs->lasttarget = -1; + fs->jpc = NO_JUMP; + fs->freereg = 0; + fs->nk = 0; + fs->np = 0; + fs->nlocvars = 0; + fs->nactvar = 0; + fs->bl = NULL; + f->source = ls->source; + f->maxstacksize = 2; /* registers 0/1 are always valid */ + fs->h = luaH_new(L, 0, 0); + /* anchor table of constants and prototype (to avoid being collected) */ + sethvalue2s(L, L->top, fs->h); + incr_top(L); + setptvalue2s(L, L->top, f); + incr_top(L); +} + + +static void close_func (LexState *ls) { + lua_State *L = ls->L; + FuncState *fs = ls->fs; + Proto *f = fs->f; + removevars(ls, 0); + luaK_ret(fs, 0, 0); /* final return */ + luaM_reallocvector(L, f->code, f->sizecode, fs->pc, Instruction); + f->sizecode = fs->pc; + luaM_reallocvector(L, f->lineinfo, f->sizelineinfo, fs->pc, int); + f->sizelineinfo = fs->pc; + luaM_reallocvector(L, f->k, f->sizek, fs->nk, TValue); + f->sizek = fs->nk; + luaM_reallocvector(L, f->p, f->sizep, fs->np, Proto *); + f->sizep = fs->np; + luaM_reallocvector(L, f->locvars, f->sizelocvars, fs->nlocvars, LocVar); + f->sizelocvars = fs->nlocvars; + luaM_reallocvector(L, f->upvalues, f->sizeupvalues, f->nups, TString *); + f->sizeupvalues = f->nups; + lua_assert(luaG_checkcode(f)); + lua_assert(fs->bl == NULL); + ls->fs = fs->prev; + L->top -= 2; /* remove table and prototype from the stack */ + /* last token read was anchored in defunct function; must reanchor it */ + if (fs) anchor_token(ls); +} + + +Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) { + struct LexState lexstate; + struct FuncState funcstate; + lexstate.buff = buff; + luaX_setinput(L, &lexstate, z, luaS_new(L, name)); + open_func(&lexstate, &funcstate); + funcstate.f->is_vararg = VARARG_ISVARARG; /* main func. is always vararg */ + luaX_next(&lexstate); /* read first token */ + chunk(&lexstate); + check(&lexstate, TK_EOS); + close_func(&lexstate); + lua_assert(funcstate.prev == NULL); + lua_assert(funcstate.f->nups == 0); + lua_assert(lexstate.fs == NULL); + return funcstate.f; +} + + + +/*============================================================*/ +/* GRAMMAR RULES */ +/*============================================================*/ + + +static void field (LexState *ls, expdesc *v) { + /* field -> ['.' | ':'] NAME */ + FuncState *fs = ls->fs; + expdesc key; + luaK_exp2anyreg(fs, v); + luaX_next(ls); /* skip the dot or colon */ + checkname(ls, &key); + luaK_indexed(fs, v, &key); +} + + +static void yindex (LexState *ls, expdesc *v) { + /* index -> '[' expr ']' */ + luaX_next(ls); /* skip the '[' */ + expr(ls, v); + luaK_exp2val(ls->fs, v); + checknext(ls, ']'); +} + + +/* +** {====================================================================== +** Rules for Constructors +** ======================================================================= +*/ + + +struct ConsControl { + expdesc v; /* last list item read */ + expdesc *t; /* table descriptor */ + int nh; /* total number of `record' elements */ + int na; /* total number of array elements */ + int tostore; /* number of array elements pending to be stored */ +}; + + +static void recfield (LexState *ls, struct ConsControl *cc) { + /* recfield -> (NAME | `['exp1`]') = exp1 */ + FuncState *fs = ls->fs; + int reg = ls->fs->freereg; + expdesc key, val; + int rkkey; + if (ls->t.token == TK_NAME) { + luaY_checklimit(fs, cc->nh, MAX_INT, "items in a constructor"); + checkname(ls, &key); + } + else /* ls->t.token == '[' */ + yindex(ls, &key); + cc->nh++; + checknext(ls, '='); + rkkey = luaK_exp2RK(fs, &key); + expr(ls, &val); + luaK_codeABC(fs, OP_SETTABLE, cc->t->u.s.info, rkkey, luaK_exp2RK(fs, &val)); + fs->freereg = reg; /* free registers */ +} + + +static void closelistfield (FuncState *fs, struct ConsControl *cc) { + if (cc->v.k == VVOID) return; /* there is no list item */ + luaK_exp2nextreg(fs, &cc->v); + cc->v.k = VVOID; + if (cc->tostore == LFIELDS_PER_FLUSH) { + luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore); /* flush */ + cc->tostore = 0; /* no more items pending */ + } +} + + +static void lastlistfield (FuncState *fs, struct ConsControl *cc) { + if (cc->tostore == 0) return; + if (hasmultret(cc->v.k)) { + luaK_setmultret(fs, &cc->v); + luaK_setlist(fs, cc->t->u.s.info, cc->na, LUA_MULTRET); + cc->na--; /* do not count last expression (unknown number of elements) */ + } + else { + if (cc->v.k != VVOID) + luaK_exp2nextreg(fs, &cc->v); + luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore); + } +} + + +static void listfield (LexState *ls, struct ConsControl *cc) { + expr(ls, &cc->v); + luaY_checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor"); + cc->na++; + cc->tostore++; +} + + +static void constructor (LexState *ls, expdesc *t) { + /* constructor -> ?? */ + FuncState *fs = ls->fs; + int line = ls->linenumber; + int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); + struct ConsControl cc; + cc.na = cc.nh = cc.tostore = 0; + cc.t = t; + init_exp(t, VRELOCABLE, pc); + init_exp(&cc.v, VVOID, 0); /* no value (yet) */ + luaK_exp2nextreg(ls->fs, t); /* fix it at stack top (for gc) */ + checknext(ls, '{'); + do { + lua_assert(cc.v.k == VVOID || cc.tostore > 0); + if (ls->t.token == '}') break; + closelistfield(fs, &cc); + switch(ls->t.token) { + case TK_NAME: { /* may be listfields or recfields */ + luaX_lookahead(ls); + if (ls->lookahead.token != '=') /* expression? */ + listfield(ls, &cc); + else + recfield(ls, &cc); + break; + } + case '[': { /* constructor_item -> recfield */ + recfield(ls, &cc); + break; + } + default: { /* constructor_part -> listfield */ + listfield(ls, &cc); + break; + } + } + } while (testnext(ls, ',') || testnext(ls, ';')); + check_match(ls, '}', '{', line); + lastlistfield(fs, &cc); + SETARG_B(fs->f->code[pc], luaO_int2fb(cc.na)); /* set initial array size */ + SETARG_C(fs->f->code[pc], luaO_int2fb(cc.nh)); /* set initial table size */ +} + +/* }====================================================================== */ + + + +static void parlist (LexState *ls) { + /* parlist -> [ param { `,' param } ] */ + FuncState *fs = ls->fs; + Proto *f = fs->f; + int nparams = 0; + f->is_vararg = 0; + if (ls->t.token != ')') { /* is `parlist' not empty? */ + do { + switch (ls->t.token) { + case TK_NAME: { /* param -> NAME */ + new_localvar(ls, str_checkname(ls), nparams++); + break; + } + case TK_DOTS: { /* param -> `...' */ + luaX_next(ls); +#if defined(LUA_COMPAT_VARARG) + /* use `arg' as default name */ + new_localvarliteral(ls, "arg", nparams++); + f->is_vararg = VARARG_HASARG | VARARG_NEEDSARG; +#endif + f->is_vararg |= VARARG_ISVARARG; + break; + } + default: luaX_syntaxerror(ls, "<name> or " LUA_QL("...") " expected"); + } + } while (!f->is_vararg && testnext(ls, ',')); + } + adjustlocalvars(ls, nparams); + f->numparams = cast_byte(fs->nactvar - (f->is_vararg & VARARG_HASARG)); + luaK_reserveregs(fs, fs->nactvar); /* reserve register for parameters */ +} + + +static void body (LexState *ls, expdesc *e, int needself, int line) { + /* body -> `(' parlist `)' chunk END */ + FuncState new_fs; + open_func(ls, &new_fs); + new_fs.f->linedefined = line; + checknext(ls, '('); + if (needself) { + new_localvarliteral(ls, "self", 0); + adjustlocalvars(ls, 1); + } + parlist(ls); + checknext(ls, ')'); + chunk(ls); + new_fs.f->lastlinedefined = ls->linenumber; + check_match(ls, TK_END, TK_FUNCTION, line); + close_func(ls); + pushclosure(ls, &new_fs, e); +} + + +static int explist1 (LexState *ls, expdesc *v) { + /* explist1 -> expr { `,' expr } */ + int n = 1; /* at least one expression */ + expr(ls, v); + while (testnext(ls, ',')) { + luaK_exp2nextreg(ls->fs, v); + expr(ls, v); + n++; + } + return n; +} + + +static void funcargs (LexState *ls, expdesc *f) { + FuncState *fs = ls->fs; + expdesc args; + int base, nparams; + int line = ls->linenumber; + switch (ls->t.token) { + case '(': { /* funcargs -> `(' [ explist1 ] `)' */ + if (line != ls->lastline) + luaX_syntaxerror(ls,"ambiguous syntax (function call x new statement)"); + luaX_next(ls); + if (ls->t.token == ')') /* arg list is empty? */ + args.k = VVOID; + else { + explist1(ls, &args); + luaK_setmultret(fs, &args); + } + check_match(ls, ')', '(', line); + break; + } + case '{': { /* funcargs -> constructor */ + constructor(ls, &args); + break; + } + case TK_STRING: { /* funcargs -> STRING */ + codestring(ls, &args, ls->t.seminfo.ts); + luaX_next(ls); /* must use `seminfo' before `next' */ + break; + } + default: { + luaX_syntaxerror(ls, "function arguments expected"); + return; + } + } + lua_assert(f->k == VNONRELOC); + base = f->u.s.info; /* base register for call */ + if (hasmultret(args.k)) + nparams = LUA_MULTRET; /* open call */ + else { + if (args.k != VVOID) + luaK_exp2nextreg(fs, &args); /* close last argument */ + nparams = fs->freereg - (base+1); + } + init_exp(f, VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2)); + luaK_fixline(fs, line); + fs->freereg = base+1; /* call remove function and arguments and leaves + (unless changed) one result */ +} + + + + +/* +** {====================================================================== +** Expression parsing +** ======================================================================= +*/ + + +static void prefixexp (LexState *ls, expdesc *v) { + /* prefixexp -> NAME | '(' expr ')' */ + switch (ls->t.token) { + case '(': { + int line = ls->linenumber; + luaX_next(ls); + expr(ls, v); + check_match(ls, ')', '(', line); + luaK_dischargevars(ls->fs, v); + return; + } + case TK_NAME: { + singlevar(ls, v); + return; + } + default: { + luaX_syntaxerror(ls, "unexpected symbol"); + return; + } + } +} + + +static void primaryexp (LexState *ls, expdesc *v) { + /* primaryexp -> + prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs } */ + FuncState *fs = ls->fs; + prefixexp(ls, v); + for (;;) { + switch (ls->t.token) { + case '.': { /* field */ + field(ls, v); + break; + } + case '[': { /* `[' exp1 `]' */ + expdesc key; + luaK_exp2anyreg(fs, v); + yindex(ls, &key); + luaK_indexed(fs, v, &key); + break; + } + case ':': { /* `:' NAME funcargs */ + expdesc key; + luaX_next(ls); + checkname(ls, &key); + luaK_self(fs, v, &key); + funcargs(ls, v); + break; + } + case '(': case TK_STRING: case '{': { /* funcargs */ + luaK_exp2nextreg(fs, v); + funcargs(ls, v); + break; + } + default: return; + } + } +} + + +static void simpleexp (LexState *ls, expdesc *v) { + /* simpleexp -> NUMBER | STRING | NIL | true | false | ... | + constructor | FUNCTION body | primaryexp */ + switch (ls->t.token) { + case TK_NUMBER: { + init_exp(v, VKNUM, 0); + v->u.nval = ls->t.seminfo.r; + break; + } + case TK_STRING: { + codestring(ls, v, ls->t.seminfo.ts); + break; + } + case TK_NIL: { + init_exp(v, VNIL, 0); + break; + } + case TK_TRUE: { + init_exp(v, VTRUE, 0); + break; + } + case TK_FALSE: { + init_exp(v, VFALSE, 0); + break; + } + case TK_DOTS: { /* vararg */ + FuncState *fs = ls->fs; + check_condition(ls, fs->f->is_vararg, + "cannot use " LUA_QL("...") " outside a vararg function"); + fs->f->is_vararg &= ~VARARG_NEEDSARG; /* don't need 'arg' */ + init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 1, 0)); + break; + } + case '{': { /* constructor */ + constructor(ls, v); + return; + } + case TK_FUNCTION: { + luaX_next(ls); + body(ls, v, 0, ls->linenumber); + return; + } + default: { + primaryexp(ls, v); + return; + } + } + luaX_next(ls); +} + + +static UnOpr getunopr (int op) { + switch (op) { + case TK_NOT: return OPR_NOT; + case '-': return OPR_MINUS; + case '#': return OPR_LEN; + default: return OPR_NOUNOPR; + } +} + + +static BinOpr getbinopr (int op) { + switch (op) { + case '+': return OPR_ADD; + case '-': return OPR_SUB; + case '*': return OPR_MUL; + case '/': return OPR_DIV; + case '%': return OPR_MOD; + case '^': return OPR_POW; + case TK_CONCAT: return OPR_CONCAT; + case TK_NE: return OPR_NE; + case TK_EQ: return OPR_EQ; + case '<': return OPR_LT; + case TK_LE: return OPR_LE; + case '>': return OPR_GT; + case TK_GE: return OPR_GE; + case TK_AND: return OPR_AND; + case TK_OR: return OPR_OR; + default: return OPR_NOBINOPR; + } +} + + +static const struct { + lu_byte left; /* left priority for each binary operator */ + lu_byte right; /* right priority */ +} priority[] = { /* ORDER OPR */ + {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, /* `+' `-' `/' `%' */ + {10, 9}, {5, 4}, /* power and concat (right associative) */ + {3, 3}, {3, 3}, /* equality and inequality */ + {3, 3}, {3, 3}, {3, 3}, {3, 3}, /* order */ + {2, 2}, {1, 1} /* logical (and/or) */ +}; + +#define UNARY_PRIORITY 8 /* priority for unary operators */ + + +/* +** subexpr -> (simpleexp | unop subexpr) { binop subexpr } +** where `binop' is any binary operator with a priority higher than `limit' +*/ +static BinOpr subexpr (LexState *ls, expdesc *v, unsigned int limit) { + BinOpr op; + UnOpr uop; + enterlevel(ls); + uop = getunopr(ls->t.token); + if (uop != OPR_NOUNOPR) { + luaX_next(ls); + subexpr(ls, v, UNARY_PRIORITY); + luaK_prefix(ls->fs, uop, v); + } + else simpleexp(ls, v); + /* expand while operators have priorities higher than `limit' */ + op = getbinopr(ls->t.token); + while (op != OPR_NOBINOPR && priority[op].left > limit) { + expdesc v2; + BinOpr nextop; + luaX_next(ls); + luaK_infix(ls->fs, op, v); + /* read sub-expression with higher priority */ + nextop = subexpr(ls, &v2, priority[op].right); + luaK_posfix(ls->fs, op, v, &v2); + op = nextop; + } + leavelevel(ls); + return op; /* return first untreated operator */ +} + + +static void expr (LexState *ls, expdesc *v) { + subexpr(ls, v, 0); +} + +/* }==================================================================== */ + + + +/* +** {====================================================================== +** Rules for Statements +** ======================================================================= +*/ + + +static int block_follow (int token) { + switch (token) { + case TK_ELSE: case TK_ELSEIF: case TK_END: + case TK_UNTIL: case TK_EOS: + return 1; + default: return 0; + } +} + + +static void block (LexState *ls) { + /* block -> chunk */ + FuncState *fs = ls->fs; + BlockCnt bl; + enterblock(fs, &bl, 0); + chunk(ls); + lua_assert(bl.breaklist == NO_JUMP); + leaveblock(fs); +} + + +/* +** structure to chain all variables in the left-hand side of an +** assignment +*/ +struct LHS_assign { + struct LHS_assign *prev; + expdesc v; /* variable (global, local, upvalue, or indexed) */ +}; + + +/* +** check whether, in an assignment to a local variable, the local variable +** is needed in a previous assignment (to a table). If so, save original +** local value in a safe place and use this safe copy in the previous +** assignment. +*/ +static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { + FuncState *fs = ls->fs; + int extra = fs->freereg; /* eventual position to save local variable */ + int conflict = 0; + for (; lh; lh = lh->prev) { + if (lh->v.k == VINDEXED) { + if (lh->v.u.s.info == v->u.s.info) { /* conflict? */ + conflict = 1; + lh->v.u.s.info = extra; /* previous assignment will use safe copy */ + } + if (lh->v.u.s.aux == v->u.s.info) { /* conflict? */ + conflict = 1; + lh->v.u.s.aux = extra; /* previous assignment will use safe copy */ + } + } + } + if (conflict) { + luaK_codeABC(fs, OP_MOVE, fs->freereg, v->u.s.info, 0); /* make copy */ + luaK_reserveregs(fs, 1); + } +} + + +static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) { + expdesc e; + check_condition(ls, VLOCAL <= lh->v.k && lh->v.k <= VINDEXED, + "syntax error"); + if (testnext(ls, ',')) { /* assignment -> `,' primaryexp assignment */ + struct LHS_assign nv; + nv.prev = lh; + primaryexp(ls, &nv.v); + if (nv.v.k == VLOCAL) + check_conflict(ls, lh, &nv.v); + luaY_checklimit(ls->fs, nvars, LUAI_MAXCCALLS - ls->L->nCcalls, + "variables in assignment"); + assignment(ls, &nv, nvars+1); + } + else { /* assignment -> `=' explist1 */ + int nexps; + checknext(ls, '='); + nexps = explist1(ls, &e); + if (nexps != nvars) { + adjust_assign(ls, nvars, nexps, &e); + if (nexps > nvars) + ls->fs->freereg -= nexps - nvars; /* remove extra values */ + } + else { + luaK_setoneret(ls->fs, &e); /* close last expression */ + luaK_storevar(ls->fs, &lh->v, &e); + return; /* avoid default */ + } + } + init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */ + luaK_storevar(ls->fs, &lh->v, &e); +} + + +static int cond (LexState *ls) { + /* cond -> exp */ + expdesc v; + expr(ls, &v); /* read condition */ + if (v.k == VNIL) v.k = VFALSE; /* `falses' are all equal here */ + luaK_goiftrue(ls->fs, &v); + return v.f; +} + + +static void breakstat (LexState *ls) { + FuncState *fs = ls->fs; + BlockCnt *bl = fs->bl; + int upval = 0; + while (bl && !bl->isbreakable) { + upval |= bl->upval; + bl = bl->previous; + } + if (!bl) + luaX_syntaxerror(ls, "no loop to break"); + if (upval) + luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0); + luaK_concat(fs, &bl->breaklist, luaK_jump(fs)); +} + + +static void whilestat (LexState *ls, int line) { + /* whilestat -> WHILE cond DO block END */ + FuncState *fs = ls->fs; + int whileinit; + int condexit; + BlockCnt bl; + luaX_next(ls); /* skip WHILE */ + whileinit = luaK_getlabel(fs); + condexit = cond(ls); + enterblock(fs, &bl, 1); + checknext(ls, TK_DO); + block(ls); + luaK_patchlist(fs, luaK_jump(fs), whileinit); + check_match(ls, TK_END, TK_WHILE, line); + leaveblock(fs); + luaK_patchtohere(fs, condexit); /* false conditions finish the loop */ +} + + +static void repeatstat (LexState *ls, int line) { + /* repeatstat -> REPEAT block UNTIL cond */ + int condexit; + FuncState *fs = ls->fs; + int repeat_init = luaK_getlabel(fs); + BlockCnt bl1, bl2; + enterblock(fs, &bl1, 1); /* loop block */ + enterblock(fs, &bl2, 0); /* scope block */ + luaX_next(ls); /* skip REPEAT */ + chunk(ls); + check_match(ls, TK_UNTIL, TK_REPEAT, line); + condexit = cond(ls); /* read condition (inside scope block) */ + if (!bl2.upval) { /* no upvalues? */ + leaveblock(fs); /* finish scope */ + luaK_patchlist(ls->fs, condexit, repeat_init); /* close the loop */ + } + else { /* complete semantics when there are upvalues */ + breakstat(ls); /* if condition then break */ + luaK_patchtohere(ls->fs, condexit); /* else... */ + leaveblock(fs); /* finish scope... */ + luaK_patchlist(ls->fs, luaK_jump(fs), repeat_init); /* and repeat */ + } + leaveblock(fs); /* finish loop */ +} + + +static int exp1 (LexState *ls) { + expdesc e; + int k; + expr(ls, &e); + k = e.k; + luaK_exp2nextreg(ls->fs, &e); + return k; +} + + +static void forbody (LexState *ls, int base, int line, int nvars, int isnum) { + /* forbody -> DO block */ + BlockCnt bl; + FuncState *fs = ls->fs; + int prep, endfor; + adjustlocalvars(ls, 3); /* control variables */ + checknext(ls, TK_DO); + prep = isnum ? luaK_codeAsBx(fs, OP_FORPREP, base, NO_JUMP) : luaK_jump(fs); + enterblock(fs, &bl, 0); /* scope for declared variables */ + adjustlocalvars(ls, nvars); + luaK_reserveregs(fs, nvars); + block(ls); + leaveblock(fs); /* end of scope for declared variables */ + luaK_patchtohere(fs, prep); + endfor = (isnum) ? luaK_codeAsBx(fs, OP_FORLOOP, base, NO_JUMP) : + luaK_codeABC(fs, OP_TFORLOOP, base, 0, nvars); + luaK_fixline(fs, line); /* pretend that `OP_FOR' starts the loop */ + luaK_patchlist(fs, (isnum ? endfor : luaK_jump(fs)), prep + 1); +} + + +static void fornum (LexState *ls, TString *varname, int line) { + /* fornum -> NAME = exp1,exp1[,exp1] forbody */ + FuncState *fs = ls->fs; + int base = fs->freereg; + new_localvarliteral(ls, "(for index)", 0); + new_localvarliteral(ls, "(for limit)", 1); + new_localvarliteral(ls, "(for step)", 2); + new_localvar(ls, varname, 3); + checknext(ls, '='); + exp1(ls); /* initial value */ + checknext(ls, ','); + exp1(ls); /* limit */ + if (testnext(ls, ',')) + exp1(ls); /* optional step */ + else { /* default step = 1 */ + luaK_codeABx(fs, OP_LOADK, fs->freereg, luaK_numberK(fs, 1)); + luaK_reserveregs(fs, 1); + } + forbody(ls, base, line, 1, 1); +} + + +static void forlist (LexState *ls, TString *indexname) { + /* forlist -> NAME {,NAME} IN explist1 forbody */ + FuncState *fs = ls->fs; + expdesc e; + int nvars = 0; + int line; + int base = fs->freereg; + /* create control variables */ + new_localvarliteral(ls, "(for generator)", nvars++); + new_localvarliteral(ls, "(for state)", nvars++); + new_localvarliteral(ls, "(for control)", nvars++); + /* create declared variables */ + new_localvar(ls, indexname, nvars++); + while (testnext(ls, ',')) + new_localvar(ls, str_checkname(ls), nvars++); + checknext(ls, TK_IN); + line = ls->linenumber; + adjust_assign(ls, 3, explist1(ls, &e), &e); + luaK_checkstack(fs, 3); /* extra space to call generator */ + forbody(ls, base, line, nvars - 3, 0); +} + + +static void forstat (LexState *ls, int line) { + /* forstat -> FOR (fornum | forlist) END */ + FuncState *fs = ls->fs; + TString *varname; + BlockCnt bl; + enterblock(fs, &bl, 1); /* scope for loop and control variables */ + luaX_next(ls); /* skip `for' */ + varname = str_checkname(ls); /* first variable name */ + switch (ls->t.token) { + case '=': fornum(ls, varname, line); break; + case ',': case TK_IN: forlist(ls, varname); break; + default: luaX_syntaxerror(ls, LUA_QL("=") " or " LUA_QL("in") " expected"); + } + check_match(ls, TK_END, TK_FOR, line); + leaveblock(fs); /* loop scope (`break' jumps to this point) */ +} + + +static int test_then_block (LexState *ls) { + /* test_then_block -> [IF | ELSEIF] cond THEN block */ + int condexit; + luaX_next(ls); /* skip IF or ELSEIF */ + condexit = cond(ls); + checknext(ls, TK_THEN); + block(ls); /* `then' part */ + return condexit; +} + + +static void ifstat (LexState *ls, int line) { + /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */ + FuncState *fs = ls->fs; + int flist; + int escapelist = NO_JUMP; + flist = test_then_block(ls); /* IF cond THEN block */ + while (ls->t.token == TK_ELSEIF) { + luaK_concat(fs, &escapelist, luaK_jump(fs)); + luaK_patchtohere(fs, flist); + flist = test_then_block(ls); /* ELSEIF cond THEN block */ + } + if (ls->t.token == TK_ELSE) { + luaK_concat(fs, &escapelist, luaK_jump(fs)); + luaK_patchtohere(fs, flist); + luaX_next(ls); /* skip ELSE (after patch, for correct line info) */ + block(ls); /* `else' part */ + } + else + luaK_concat(fs, &escapelist, flist); + luaK_patchtohere(fs, escapelist); + check_match(ls, TK_END, TK_IF, line); +} + + +static void localfunc (LexState *ls) { + expdesc v, b; + FuncState *fs = ls->fs; + new_localvar(ls, str_checkname(ls), 0); + init_exp(&v, VLOCAL, fs->freereg); + luaK_reserveregs(fs, 1); + adjustlocalvars(ls, 1); + body(ls, &b, 0, ls->linenumber); + luaK_storevar(fs, &v, &b); + /* debug information will only see the variable after this point! */ + getlocvar(fs, fs->nactvar - 1).startpc = fs->pc; +} + + +static void localstat (LexState *ls) { + /* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */ + int nvars = 0; + int nexps; + expdesc e; + do { + new_localvar(ls, str_checkname(ls), nvars++); + } while (testnext(ls, ',')); + if (testnext(ls, '=')) + nexps = explist1(ls, &e); + else { + e.k = VVOID; + nexps = 0; + } + adjust_assign(ls, nvars, nexps, &e); + adjustlocalvars(ls, nvars); +} + + +static int funcname (LexState *ls, expdesc *v) { + /* funcname -> NAME {field} [`:' NAME] */ + int needself = 0; + singlevar(ls, v); + while (ls->t.token == '.') + field(ls, v); + if (ls->t.token == ':') { + needself = 1; + field(ls, v); + } + return needself; +} + + +static void funcstat (LexState *ls, int line) { + /* funcstat -> FUNCTION funcname body */ + int needself; + expdesc v, b; + luaX_next(ls); /* skip FUNCTION */ + needself = funcname(ls, &v); + body(ls, &b, needself, line); + luaK_storevar(ls->fs, &v, &b); + luaK_fixline(ls->fs, line); /* definition `happens' in the first line */ +} + + +static void exprstat (LexState *ls) { + /* stat -> func | assignment */ + FuncState *fs = ls->fs; + struct LHS_assign v; + primaryexp(ls, &v.v); + if (v.v.k == VCALL) /* stat -> func */ + SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ + else { /* stat -> assignment */ + v.prev = NULL; + assignment(ls, &v, 1); + } +} + + +static void retstat (LexState *ls) { + /* stat -> RETURN explist */ + FuncState *fs = ls->fs; + expdesc e; + int first, nret; /* registers with returned values */ + luaX_next(ls); /* skip RETURN */ + if (block_follow(ls->t.token) || ls->t.token == ';') + first = nret = 0; /* return no values */ + else { + nret = explist1(ls, &e); /* optional return values */ + if (hasmultret(e.k)) { + luaK_setmultret(fs, &e); + if (e.k == VCALL && nret == 1) { /* tail call? */ + SET_OPCODE(getcode(fs,&e), OP_TAILCALL); + lua_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar); + } + first = fs->nactvar; + nret = LUA_MULTRET; /* return all values */ + } + else { + if (nret == 1) /* only one single value? */ + first = luaK_exp2anyreg(fs, &e); + else { + luaK_exp2nextreg(fs, &e); /* values must go to the `stack' */ + first = fs->nactvar; /* return all `active' values */ + lua_assert(nret == fs->freereg - first); + } + } + } + luaK_ret(fs, first, nret); +} + + +static int statement (LexState *ls) { + int line = ls->linenumber; /* may be needed for error messages */ + switch (ls->t.token) { + case TK_IF: { /* stat -> ifstat */ + ifstat(ls, line); + return 0; + } + case TK_WHILE: { /* stat -> whilestat */ + whilestat(ls, line); + return 0; + } + case TK_DO: { /* stat -> DO block END */ + luaX_next(ls); /* skip DO */ + block(ls); + check_match(ls, TK_END, TK_DO, line); + return 0; + } + case TK_FOR: { /* stat -> forstat */ + forstat(ls, line); + return 0; + } + case TK_REPEAT: { /* stat -> repeatstat */ + repeatstat(ls, line); + return 0; + } + case TK_FUNCTION: { + funcstat(ls, line); /* stat -> funcstat */ + return 0; + } + case TK_LOCAL: { /* stat -> localstat */ + luaX_next(ls); /* skip LOCAL */ + if (testnext(ls, TK_FUNCTION)) /* local function? */ + localfunc(ls); + else + localstat(ls); + return 0; + } + case TK_RETURN: { /* stat -> retstat */ + retstat(ls); + return 1; /* must be last statement */ + } + case TK_BREAK: { /* stat -> breakstat */ + luaX_next(ls); /* skip BREAK */ + breakstat(ls); + return 1; /* must be last statement */ + } + default: { + exprstat(ls); + return 0; /* to avoid warnings */ + } + } +} + + +static void chunk (LexState *ls) { + /* chunk -> { stat [`;'] } */ + int islast = 0; + enterlevel(ls); + while (!islast && !block_follow(ls->t.token)) { + islast = statement(ls); + testnext(ls, ';'); + lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && + ls->fs->freereg >= ls->fs->nactvar); + ls->fs->freereg = ls->fs->nactvar; /* free registers */ + } + leavelevel(ls); +} + +/* }====================================================================== */ diff --git a/engines/sword25/util/lua/lparser.h b/engines/sword25/util/lua/lparser.h new file mode 100644 index 0000000000..f9b8e24913 --- /dev/null +++ b/engines/sword25/util/lua/lparser.h @@ -0,0 +1,82 @@ +/* +** $Id$ +** Lua Parser +** See Copyright Notice in lua.h +*/ + +#ifndef lparser_h +#define lparser_h + +#include "llimits.h" +#include "lobject.h" +#include "lzio.h" + + +/* +** Expression descriptor +*/ + +typedef enum { + VVOID, /* no value */ + VNIL, + VTRUE, + VFALSE, + VK, /* info = index of constant in `k' */ + VKNUM, /* nval = numerical value */ + VLOCAL, /* info = local register */ + VUPVAL, /* info = index of upvalue in `upvalues' */ + VGLOBAL, /* info = index of table; aux = index of global name in `k' */ + VINDEXED, /* info = table register; aux = index register (or `k') */ + VJMP, /* info = instruction pc */ + VRELOCABLE, /* info = instruction pc */ + VNONRELOC, /* info = result register */ + VCALL, /* info = instruction pc */ + VVARARG /* info = instruction pc */ +} expkind; + +typedef struct expdesc { + expkind k; + union { + struct { int info, aux; } s; + lua_Number nval; + } u; + int t; /* patch list of `exit when true' */ + int f; /* patch list of `exit when false' */ +} expdesc; + + +typedef struct upvaldesc { + lu_byte k; + lu_byte info; +} upvaldesc; + + +struct BlockCnt; /* defined in lparser.c */ + + +/* state needed to generate code for a given function */ +typedef struct FuncState { + Proto *f; /* current function header */ + Table *h; /* table to find (and reuse) elements in `k' */ + struct FuncState *prev; /* enclosing function */ + struct LexState *ls; /* lexical state */ + struct lua_State *L; /* copy of the Lua state */ + struct BlockCnt *bl; /* chain of current blocks */ + int pc; /* next position to code (equivalent to `ncode') */ + int lasttarget; /* `pc' of last `jump target' */ + int jpc; /* list of pending jumps to `pc' */ + int freereg; /* first free register */ + int nk; /* number of elements in `k' */ + int np; /* number of elements in `p' */ + short nlocvars; /* number of elements in `locvars' */ + lu_byte nactvar; /* number of active local variables */ + upvaldesc upvalues[LUAI_MAXUPVALUES]; /* upvalues */ + unsigned short actvar[LUAI_MAXVARS]; /* declared-variable stack */ +} FuncState; + + +LUAI_FUNC Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, + const char *name); + + +#endif diff --git a/engines/sword25/util/lua/lstate.cpp b/engines/sword25/util/lua/lstate.cpp new file mode 100644 index 0000000000..495d75c8a6 --- /dev/null +++ b/engines/sword25/util/lua/lstate.cpp @@ -0,0 +1,214 @@ +/* +** $Id$ +** Global State +** See Copyright Notice in lua.h +*/ + + +#include <stddef.h> + +#define lstate_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "llex.h" +#include "lmem.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + +#define state_size(x) (sizeof(x) + LUAI_EXTRASPACE) +#define fromstate(l) (cast(lu_byte *, (l)) - LUAI_EXTRASPACE) +#define tostate(l) (cast(lua_State *, cast(lu_byte *, l) + LUAI_EXTRASPACE)) + + +/* +** Main thread combines a thread state and the global state +*/ +typedef struct LG { + lua_State l; + global_State g; +} LG; + + + +static void stack_init (lua_State *L1, lua_State *L) { + /* initialize CallInfo array */ + L1->base_ci = luaM_newvector(L, BASIC_CI_SIZE, CallInfo); + L1->ci = L1->base_ci; + L1->size_ci = BASIC_CI_SIZE; + L1->end_ci = L1->base_ci + L1->size_ci - 1; + /* initialize stack array */ + L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue); + L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK; + L1->top = L1->stack; + L1->stack_last = L1->stack+(L1->stacksize - EXTRA_STACK)-1; + /* initialize first ci */ + L1->ci->func = L1->top; + setnilvalue(L1->top++); /* `function' entry for this `ci' */ + L1->base = L1->ci->base = L1->top; + L1->ci->top = L1->top + LUA_MINSTACK; +} + + +static void freestack (lua_State *L, lua_State *L1) { + luaM_freearray(L, L1->base_ci, L1->size_ci, CallInfo); + luaM_freearray(L, L1->stack, L1->stacksize, TValue); +} + + +/* +** open parts that may cause memory-allocation errors +*/ +static void f_luaopen (lua_State *L, void *ud) { + global_State *g = G(L); + UNUSED(ud); + stack_init(L, L); /* init stack */ + sethvalue(L, gt(L), luaH_new(L, 0, 2)); /* table of globals */ + sethvalue(L, registry(L), luaH_new(L, 0, 2)); /* registry */ + luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ + luaT_init(L); + luaX_init(L); + luaS_fix(luaS_newliteral(L, MEMERRMSG)); + g->GCthreshold = 4*g->totalbytes; +} + + +static void preinit_state (lua_State *L, global_State *g) { + G(L) = g; + L->stack = NULL; + L->stacksize = 0; + L->errorJmp = NULL; + L->hook = NULL; + L->hookmask = 0; + L->basehookcount = 0; + L->allowhook = 1; + resethookcount(L); + L->openupval = NULL; + L->size_ci = 0; + L->nCcalls = L->baseCcalls = 0; + L->status = 0; + L->base_ci = L->ci = NULL; + L->savedpc = NULL; + L->errfunc = 0; + setnilvalue(gt(L)); +} + + +static void close_state (lua_State *L) { + global_State *g = G(L); + luaF_close(L, L->stack); /* close all upvalues for this thread */ + luaC_freeall(L); /* collect all objects */ + lua_assert(g->rootgc == obj2gco(L)); + lua_assert(g->strt.nuse == 0); + luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size, TString *); + luaZ_freebuffer(L, &g->buff); + freestack(L, L); + lua_assert(g->totalbytes == sizeof(LG)); + (*g->frealloc)(g->ud, fromstate(L), state_size(LG), 0); +} + + +lua_State *luaE_newthread (lua_State *L) { + lua_State *L1 = tostate(luaM_malloc(L, state_size(lua_State))); + luaC_link(L, obj2gco(L1), LUA_TTHREAD); + preinit_state(L1, G(L)); + stack_init(L1, L); /* init stack */ + setobj2n(L, gt(L1), gt(L)); /* share table of globals */ + L1->hookmask = L->hookmask; + L1->basehookcount = L->basehookcount; + L1->hook = L->hook; + resethookcount(L1); + lua_assert(iswhite(obj2gco(L1))); + return L1; +} + + +void luaE_freethread (lua_State *L, lua_State *L1) { + luaF_close(L1, L1->stack); /* close all upvalues for this thread */ + lua_assert(L1->openupval == NULL); + luai_userstatefree(L1); + freestack(L, L1); + luaM_freemem(L, fromstate(L1), state_size(lua_State)); +} + + +LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { + int i; + lua_State *L; + global_State *g; + void *l = (*f)(ud, NULL, 0, state_size(LG)); + if (l == NULL) return NULL; + L = tostate(l); + g = &((LG *)L)->g; + L->next = NULL; + L->tt = LUA_TTHREAD; + g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT); + L->marked = luaC_white(g); + set2bits(L->marked, FIXEDBIT, SFIXEDBIT); + preinit_state(L, g); + g->frealloc = f; + g->ud = ud; + g->mainthread = L; + g->uvhead.u.l.prev = &g->uvhead; + g->uvhead.u.l.next = &g->uvhead; + g->GCthreshold = 0; /* mark it as unfinished state */ + g->strt.size = 0; + g->strt.nuse = 0; + g->strt.hash = NULL; + setnilvalue(registry(L)); + luaZ_initbuffer(L, &g->buff); + g->panic = NULL; + g->gcstate = GCSpause; + g->rootgc = obj2gco(L); + g->sweepstrgc = 0; + g->sweepgc = &g->rootgc; + g->gray = NULL; + g->grayagain = NULL; + g->weak = NULL; + g->tmudata = NULL; + g->totalbytes = sizeof(LG); + g->gcpause = LUAI_GCPAUSE; + g->gcstepmul = LUAI_GCMUL; + g->gcdept = 0; + for (i=0; i<NUM_TAGS; i++) g->mt[i] = NULL; + if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0) { + /* memory allocation error: free partial state */ + close_state(L); + L = NULL; + } + else + luai_userstateopen(L); + return L; +} + + +static void callallgcTM (lua_State *L, void *ud) { + UNUSED(ud); + luaC_callGCTM(L); /* call GC metamethods for all udata */ +} + + +LUA_API void lua_close (lua_State *L) { + L = G(L)->mainthread; /* only the main thread can be closed */ + lua_lock(L); + luaF_close(L, L->stack); /* close all upvalues for this thread */ + luaC_separateudata(L, 1); /* separate udata that have GC metamethods */ + L->errfunc = 0; /* no error function during GC metamethods */ + do { /* repeat until no more errors */ + L->ci = L->base_ci; + L->base = L->top = L->ci->base; + L->nCcalls = L->baseCcalls = 0; + } while (luaD_rawrunprotected(L, callallgcTM, NULL) != 0); + lua_assert(G(L)->tmudata == NULL); + luai_userstateclose(L); + close_state(L); +} + diff --git a/engines/sword25/util/lua/lstate.h b/engines/sword25/util/lua/lstate.h new file mode 100644 index 0000000000..94a6249461 --- /dev/null +++ b/engines/sword25/util/lua/lstate.h @@ -0,0 +1,169 @@ +/* +** $Id$ +** Global State +** See Copyright Notice in lua.h +*/ + +#ifndef lstate_h +#define lstate_h + +#include "lua.h" + +#include "lobject.h" +#include "ltm.h" +#include "lzio.h" + + + +struct lua_longjmp; /* defined in ldo.c */ + + +/* table of globals */ +#define gt(L) (&L->l_gt) + +/* registry */ +#define registry(L) (&G(L)->l_registry) + + +/* extra stack space to handle TM calls and some other extras */ +#define EXTRA_STACK 5 + + +#define BASIC_CI_SIZE 8 + +#define BASIC_STACK_SIZE (2*LUA_MINSTACK) + + + +typedef struct stringtable { + GCObject **hash; + lu_int32 nuse; /* number of elements */ + int size; +} stringtable; + + +/* +** informations about a call +*/ +typedef struct CallInfo { + StkId base; /* base for this function */ + StkId func; /* function index in the stack */ + StkId top; /* top for this function */ + const Instruction *savedpc; + int nresults; /* expected number of results from this function */ + int tailcalls; /* number of tail calls lost under this entry */ +} CallInfo; + + + +#define curr_func(L) (clvalue(L->ci->func)) +#define ci_func(ci) (clvalue((ci)->func)) +#define f_isLua(ci) (!ci_func(ci)->c.isC) +#define isLua(ci) (ttisfunction((ci)->func) && f_isLua(ci)) + + +/* +** `global state', shared by all threads of this state +*/ +typedef struct global_State { + stringtable strt; /* hash table for strings */ + lua_Alloc frealloc; /* function to reallocate memory */ + void *ud; /* auxiliary data to `frealloc' */ + lu_byte currentwhite; + lu_byte gcstate; /* state of garbage collector */ + int sweepstrgc; /* position of sweep in `strt' */ + GCObject *rootgc; /* list of all collectable objects */ + GCObject **sweepgc; /* position of sweep in `rootgc' */ + GCObject *gray; /* list of gray objects */ + GCObject *grayagain; /* list of objects to be traversed atomically */ + GCObject *weak; /* list of weak tables (to be cleared) */ + GCObject *tmudata; /* last element of list of userdata to be GC */ + Mbuffer buff; /* temporary buffer for string concatentation */ + lu_mem GCthreshold; + lu_mem totalbytes; /* number of bytes currently allocated */ + lu_mem estimate; /* an estimate of number of bytes actually in use */ + lu_mem gcdept; /* how much GC is `behind schedule' */ + int gcpause; /* size of pause between successive GCs */ + int gcstepmul; /* GC `granularity' */ + lua_CFunction panic; /* to be called in unprotected errors */ + TValue l_registry; + struct lua_State *mainthread; + UpVal uvhead; /* head of double-linked list of all open upvalues */ + struct Table *mt[NUM_TAGS]; /* metatables for basic types */ + TString *tmname[TM_N]; /* array with tag-method names */ +} global_State; + + +/* +** `per thread' state +*/ +struct lua_State { + CommonHeader; + lu_byte status; + StkId top; /* first free slot in the stack */ + StkId base; /* base of current function */ + global_State *l_G; + CallInfo *ci; /* call info for current function */ + const Instruction *savedpc; /* `savedpc' of current function */ + StkId stack_last; /* last free slot in the stack */ + StkId stack; /* stack base */ + CallInfo *end_ci; /* points after end of ci array*/ + CallInfo *base_ci; /* array of CallInfo's */ + int stacksize; + int size_ci; /* size of array `base_ci' */ + unsigned short nCcalls; /* number of nested C calls */ + unsigned short baseCcalls; /* nested C calls when resuming coroutine */ + lu_byte hookmask; + lu_byte allowhook; + int basehookcount; + int hookcount; + lua_Hook hook; + TValue l_gt; /* table of globals */ + TValue env; /* temporary place for environments */ + GCObject *openupval; /* list of open upvalues in this stack */ + GCObject *gclist; + struct lua_longjmp *errorJmp; /* current error recover point */ + ptrdiff_t errfunc; /* current error handling function (stack index) */ +}; + + +#define G(L) (L->l_G) + + +/* +** Union of all collectable objects +*/ +union GCObject { + GCheader gch; + union TString ts; + union Udata u; + union Closure cl; + struct Table h; + struct Proto p; + struct UpVal uv; + struct lua_State th; /* thread */ +}; + + +/* macros to convert a GCObject into a specific value */ +#define rawgco2ts(o) check_exp((o)->gch.tt == LUA_TSTRING, &((o)->ts)) +#define gco2ts(o) (&rawgco2ts(o)->tsv) +#define rawgco2u(o) check_exp((o)->gch.tt == LUA_TUSERDATA, &((o)->u)) +#define gco2u(o) (&rawgco2u(o)->uv) +#define gco2cl(o) check_exp((o)->gch.tt == LUA_TFUNCTION, &((o)->cl)) +#define gco2h(o) check_exp((o)->gch.tt == LUA_TTABLE, &((o)->h)) +#define gco2p(o) check_exp((o)->gch.tt == LUA_TPROTO, &((o)->p)) +#define gco2uv(o) check_exp((o)->gch.tt == LUA_TUPVAL, &((o)->uv)) +#define ngcotouv(o) \ + check_exp((o) == NULL || (o)->gch.tt == LUA_TUPVAL, &((o)->uv)) +#define gco2th(o) check_exp((o)->gch.tt == LUA_TTHREAD, &((o)->th)) + +/* macro to convert any Lua object into a GCObject */ +#define obj2gco(v) (cast(GCObject *, (v))) + + +LUAI_FUNC lua_State *luaE_newthread (lua_State *L); +LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); + +#endif + diff --git a/engines/sword25/util/lua/lstring.cpp b/engines/sword25/util/lua/lstring.cpp new file mode 100644 index 0000000000..cd55cc63bf --- /dev/null +++ b/engines/sword25/util/lua/lstring.cpp @@ -0,0 +1,111 @@ +/* +** $Id$ +** String table (keeps all strings handled by Lua) +** See Copyright Notice in lua.h +*/ + + +#include <string.h> + +#define lstring_c +#define LUA_CORE + +#include "lua.h" + +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" + + + +void luaS_resize (lua_State *L, int newsize) { + GCObject **newhash; + stringtable *tb; + int i; + if (G(L)->gcstate == GCSsweepstring) + return; /* cannot resize during GC traverse */ + newhash = luaM_newvector(L, newsize, GCObject *); + tb = &G(L)->strt; + for (i=0; i<newsize; i++) newhash[i] = NULL; + /* rehash */ + for (i=0; i<tb->size; i++) { + GCObject *p = tb->hash[i]; + while (p) { /* for each node in the list */ + GCObject *next = p->gch.next; /* save next */ + unsigned int h = gco2ts(p)->hash; + int h1 = lmod(h, newsize); /* new position */ + lua_assert(cast_int(h%newsize) == lmod(h, newsize)); + p->gch.next = newhash[h1]; /* chain it */ + newhash[h1] = p; + p = next; + } + } + luaM_freearray(L, tb->hash, tb->size, TString *); + tb->size = newsize; + tb->hash = newhash; +} + + +static TString *newlstr (lua_State *L, const char *str, size_t l, + unsigned int h) { + TString *ts; + stringtable *tb; + if (l+1 > (MAX_SIZET - sizeof(TString))/sizeof(char)) + luaM_toobig(L); + ts = cast(TString *, luaM_malloc(L, (l+1)*sizeof(char)+sizeof(TString))); + ts->tsv.len = l; + ts->tsv.hash = h; + ts->tsv.marked = luaC_white(G(L)); + ts->tsv.tt = LUA_TSTRING; + ts->tsv.reserved = 0; + memcpy(ts+1, str, l*sizeof(char)); + ((char *)(ts+1))[l] = '\0'; /* ending 0 */ + tb = &G(L)->strt; + h = lmod(h, tb->size); + ts->tsv.next = tb->hash[h]; /* chain new entry */ + tb->hash[h] = obj2gco(ts); + tb->nuse++; + if (tb->nuse > cast(lu_int32, tb->size) && tb->size <= MAX_INT/2) + luaS_resize(L, tb->size*2); /* too crowded */ + return ts; +} + + +TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { + GCObject *o; + unsigned int h = cast(unsigned int, l); /* seed */ + size_t step = (l>>5)+1; /* if string is too long, don't hash all its chars */ + size_t l1; + for (l1=l; l1>=step; l1-=step) /* compute hash */ + h = h ^ ((h<<5)+(h>>2)+cast(unsigned char, str[l1-1])); + for (o = G(L)->strt.hash[lmod(h, G(L)->strt.size)]; + o != NULL; + o = o->gch.next) { + TString *ts = rawgco2ts(o); + if (ts->tsv.len == l && (memcmp(str, getstr(ts), l) == 0)) { + /* string may be dead */ + if (isdead(G(L), o)) changewhite(o); + return ts; + } + } + return newlstr(L, str, l, h); /* not found */ +} + + +Udata *luaS_newudata (lua_State *L, size_t s, Table *e) { + Udata *u; + if (s > MAX_SIZET - sizeof(Udata)) + luaM_toobig(L); + u = cast(Udata *, luaM_malloc(L, s + sizeof(Udata))); + u->uv.marked = luaC_white(G(L)); /* is not finalized */ + u->uv.tt = LUA_TUSERDATA; + u->uv.len = s; + u->uv.metatable = NULL; + u->uv.env = e; + /* chain it on udata list (after main thread) */ + u->uv.next = G(L)->mainthread->next; + G(L)->mainthread->next = obj2gco(u); + return u; +} + diff --git a/engines/sword25/util/lua/lstring.h b/engines/sword25/util/lua/lstring.h new file mode 100644 index 0000000000..c88e4c12a9 --- /dev/null +++ b/engines/sword25/util/lua/lstring.h @@ -0,0 +1,31 @@ +/* +** $Id$ +** String table (keep all strings handled by Lua) +** See Copyright Notice in lua.h +*/ + +#ifndef lstring_h +#define lstring_h + + +#include "lgc.h" +#include "lobject.h" +#include "lstate.h" + + +#define sizestring(s) (sizeof(union TString)+((s)->len+1)*sizeof(char)) + +#define sizeudata(u) (sizeof(union Udata)+(u)->len) + +#define luaS_new(L, s) (luaS_newlstr(L, s, strlen(s))) +#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ + (sizeof(s)/sizeof(char))-1)) + +#define luaS_fix(s) l_setbit((s)->tsv.marked, FIXEDBIT) + +LUAI_FUNC void luaS_resize (lua_State *L, int newsize); +LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, Table *e); +LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); + + +#endif diff --git a/engines/sword25/util/lua/lstrlib.cpp b/engines/sword25/util/lua/lstrlib.cpp new file mode 100644 index 0000000000..e5501b9b49 --- /dev/null +++ b/engines/sword25/util/lua/lstrlib.cpp @@ -0,0 +1,868 @@ +/* +** $Id$ +** Standard library for string operations and pattern-matching +** See Copyright Notice in lua.h +*/ + + +#include <ctype.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define lstrlib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +/* macro to `unsign' a character */ +#define uchar(c) ((unsigned char)(c)) + + + +static int str_len (lua_State *L) { + size_t l; + luaL_checklstring(L, 1, &l); + lua_pushinteger(L, l); + return 1; +} + + +static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) { + /* relative string position: negative means back from end */ + return (pos>=0) ? pos : (ptrdiff_t)len+pos+1; +} + + +static int str_sub (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), l); + ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), l); + if (start < 1) start = 1; + if (end > (ptrdiff_t)l) end = (ptrdiff_t)l; + if (start <= end) + lua_pushlstring(L, s+start-1, end-start+1); + else lua_pushliteral(L, ""); + return 1; +} + + +static int str_reverse (lua_State *L) { + size_t l; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + luaL_buffinit(L, &b); + while (l--) luaL_addchar(&b, s[l]); + luaL_pushresult(&b); + return 1; +} + + +static int str_lower (lua_State *L) { + size_t l; + size_t i; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + luaL_buffinit(L, &b); + for (i=0; i<l; i++) + luaL_addchar(&b, tolower(uchar(s[i]))); + luaL_pushresult(&b); + return 1; +} + + +static int str_upper (lua_State *L) { + size_t l; + size_t i; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + luaL_buffinit(L, &b); + for (i=0; i<l; i++) + luaL_addchar(&b, toupper(uchar(s[i]))); + luaL_pushresult(&b); + return 1; +} + +static int str_rep (lua_State *L) { + size_t l; + luaL_Buffer b; + const char *s = luaL_checklstring(L, 1, &l); + int n = luaL_checkint(L, 2); + luaL_buffinit(L, &b); + while (n-- > 0) + luaL_addlstring(&b, s, l); + luaL_pushresult(&b); + return 1; +} + + +static int str_byte (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + ptrdiff_t posi = posrelat(luaL_optinteger(L, 2, 1), l); + ptrdiff_t pose = posrelat(luaL_optinteger(L, 3, posi), l); + int n, i; + if (posi <= 0) posi = 1; + if ((size_t)pose > l) pose = l; + if (posi > pose) return 0; /* empty interval; return no values */ + n = (int)(pose - posi + 1); + if (posi + n <= pose) /* overflow? */ + luaL_error(L, "string slice too long"); + luaL_checkstack(L, n, "string slice too long"); + for (i=0; i<n; i++) + lua_pushinteger(L, uchar(s[posi+i-1])); + return n; +} + + +static int str_char (lua_State *L) { + int n = lua_gettop(L); /* number of arguments */ + int i; + luaL_Buffer b; + luaL_buffinit(L, &b); + for (i=1; i<=n; i++) { + int c = luaL_checkint(L, i); + luaL_argcheck(L, uchar(c) == c, i, "invalid value"); + luaL_addchar(&b, uchar(c)); + } + luaL_pushresult(&b); + return 1; +} + + +static int writer (lua_State *L, const void* b, size_t size, void* B) { + (void)L; + luaL_addlstring((luaL_Buffer*) B, (const char *)b, size); + return 0; +} + + +static int str_dump (lua_State *L) { + luaL_Buffer b; + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_settop(L, 1); + luaL_buffinit(L,&b); + if (lua_dump(L, writer, &b) != 0) + luaL_error(L, "unable to dump given function"); + luaL_pushresult(&b); + return 1; +} + + + +/* +** {====================================================== +** PATTERN MATCHING +** ======================================================= +*/ + + +#define CAP_UNFINISHED (-1) +#define CAP_POSITION (-2) + +typedef struct MatchState { + const char *src_init; /* init of source string */ + const char *src_end; /* end (`\0') of source string */ + lua_State *L; + int level; /* total number of captures (finished or unfinished) */ + struct { + const char *init; + ptrdiff_t len; + } capture[LUA_MAXCAPTURES]; +} MatchState; + + +#define L_ESC '%' +#define SPECIALS "^$*+?.([%-" + + +static int check_capture (MatchState *ms, int l) { + l -= '1'; + if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED) + return luaL_error(ms->L, "invalid capture index"); + return l; +} + + +static int capture_to_close (MatchState *ms) { + int level = ms->level; + for (level--; level>=0; level--) + if (ms->capture[level].len == CAP_UNFINISHED) return level; + return luaL_error(ms->L, "invalid pattern capture"); +} + + +static const char *classend (MatchState *ms, const char *p) { + switch (*p++) { + case L_ESC: { + if (*p == '\0') + luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")"); + return p+1; + } + case '[': { + if (*p == '^') p++; + do { /* look for a `]' */ + if (*p == '\0') + luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")"); + if (*(p++) == L_ESC && *p != '\0') + p++; /* skip escapes (e.g. `%]') */ + } while (*p != ']'); + return p+1; + } + default: { + return p; + } + } +} + + +static int match_class (int c, int cl) { + int res; + switch (tolower(cl)) { + case 'a' : res = isalpha(c); break; + case 'c' : res = iscntrl(c); break; + case 'd' : res = isdigit(c); break; + case 'l' : res = islower(c); break; + case 'p' : res = ispunct(c); break; + case 's' : res = isspace(c); break; + case 'u' : res = isupper(c); break; + case 'w' : res = isalnum(c); break; + case 'x' : res = isxdigit(c); break; + case 'z' : res = (c == 0); break; + default: return (cl == c); + } + return (islower(cl) ? res : !res); +} + + +static int matchbracketclass (int c, const char *p, const char *ec) { + int sig = 1; + if (*(p+1) == '^') { + sig = 0; + p++; /* skip the `^' */ + } + while (++p < ec) { + if (*p == L_ESC) { + p++; + if (match_class(c, uchar(*p))) + return sig; + } + else if ((*(p+1) == '-') && (p+2 < ec)) { + p+=2; + if (uchar(*(p-2)) <= c && c <= uchar(*p)) + return sig; + } + else if (uchar(*p) == c) return sig; + } + return !sig; +} + + +static int singlematch (int c, const char *p, const char *ep) { + switch (*p) { + case '.': return 1; /* matches any char */ + case L_ESC: return match_class(c, uchar(*(p+1))); + case '[': return matchbracketclass(c, p, ep-1); + default: return (uchar(*p) == c); + } +} + + +static const char *match (MatchState *ms, const char *s, const char *p); + + +static const char *matchbalance (MatchState *ms, const char *s, + const char *p) { + if (*p == 0 || *(p+1) == 0) + luaL_error(ms->L, "unbalanced pattern"); + if (*s != *p) return NULL; + else { + int b = *p; + int e = *(p+1); + int cont = 1; + while (++s < ms->src_end) { + if (*s == e) { + if (--cont == 0) return s+1; + } + else if (*s == b) cont++; + } + } + return NULL; /* string ends out of balance */ +} + + +static const char *max_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + ptrdiff_t i = 0; /* counts maximum expand for item */ + while ((s+i)<ms->src_end && singlematch(uchar(*(s+i)), p, ep)) + i++; + /* keeps trying to match with the maximum repetitions */ + while (i>=0) { + const char *res = match(ms, (s+i), ep+1); + if (res) return res; + i--; /* else didn't match; reduce 1 repetition to try again */ + } + return NULL; +} + + +static const char *min_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + for (;;) { + const char *res = match(ms, s, ep+1); + if (res != NULL) + return res; + else if (s<ms->src_end && singlematch(uchar(*s), p, ep)) + s++; /* try with one more repetition */ + else return NULL; + } +} + + +static const char *start_capture (MatchState *ms, const char *s, + const char *p, int what) { + const char *res; + int level = ms->level; + if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures"); + ms->capture[level].init = s; + ms->capture[level].len = what; + ms->level = level+1; + if ((res=match(ms, s, p)) == NULL) /* match failed? */ + ms->level--; /* undo capture */ + return res; +} + + +static const char *end_capture (MatchState *ms, const char *s, + const char *p) { + int l = capture_to_close(ms); + const char *res; + ms->capture[l].len = s - ms->capture[l].init; /* close capture */ + if ((res = match(ms, s, p)) == NULL) /* match failed? */ + ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ + return res; +} + + +static const char *match_capture (MatchState *ms, const char *s, int l) { + size_t len; + l = check_capture(ms, l); + len = ms->capture[l].len; + if ((size_t)(ms->src_end-s) >= len && + memcmp(ms->capture[l].init, s, len) == 0) + return s+len; + else return NULL; +} + + +static const char *match (MatchState *ms, const char *s, const char *p) { + init: /* using goto's to optimize tail recursion */ + switch (*p) { + case '(': { /* start capture */ + if (*(p+1) == ')') /* position capture? */ + return start_capture(ms, s, p+2, CAP_POSITION); + else + return start_capture(ms, s, p+1, CAP_UNFINISHED); + } + case ')': { /* end capture */ + return end_capture(ms, s, p+1); + } + case L_ESC: { + switch (*(p+1)) { + case 'b': { /* balanced string? */ + s = matchbalance(ms, s, p+2); + if (s == NULL) return NULL; + p+=4; goto init; /* else return match(ms, s, p+4); */ + } + case 'f': { /* frontier? */ + const char *ep; char previous; + p += 2; + if (*p != '[') + luaL_error(ms->L, "missing " LUA_QL("[") " after " + LUA_QL("%%f") " in pattern"); + ep = classend(ms, p); /* points to what is next */ + previous = (s == ms->src_init) ? '\0' : *(s-1); + if (matchbracketclass(uchar(previous), p, ep-1) || + !matchbracketclass(uchar(*s), p, ep-1)) return NULL; + p=ep; goto init; /* else return match(ms, s, ep); */ + } + default: { + if (isdigit(uchar(*(p+1)))) { /* capture results (%0-%9)? */ + s = match_capture(ms, s, uchar(*(p+1))); + if (s == NULL) return NULL; + p+=2; goto init; /* else return match(ms, s, p+2) */ + } + goto dflt; /* case default */ + } + } + } + case '\0': { /* end of pattern */ + return s; /* match succeeded */ + } + case '$': { + if (*(p+1) == '\0') /* is the `$' the last char in pattern? */ + return (s == ms->src_end) ? s : NULL; /* check end of string */ + else goto dflt; + } + default: dflt: { /* it is a pattern item */ + const char *ep = classend(ms, p); /* points to what is next */ + int m = s<ms->src_end && singlematch(uchar(*s), p, ep); + switch (*ep) { + case '?': { /* optional */ + const char *res; + if (m && ((res=match(ms, s+1, ep+1)) != NULL)) + return res; + p=ep+1; goto init; /* else return match(ms, s, ep+1); */ + } + case '*': { /* 0 or more repetitions */ + return max_expand(ms, s, p, ep); + } + case '+': { /* 1 or more repetitions */ + return (m ? max_expand(ms, s+1, p, ep) : NULL); + } + case '-': { /* 0 or more repetitions (minimum) */ + return min_expand(ms, s, p, ep); + } + default: { + if (!m) return NULL; + s++; p=ep; goto init; /* else return match(ms, s+1, ep); */ + } + } + } + } +} + + + +static const char *lmemfind (const char *s1, size_t l1, + const char *s2, size_t l2) { + if (l2 == 0) return s1; /* empty strings are everywhere */ + else if (l2 > l1) return NULL; /* avoids a negative `l1' */ + else { + const char *init; /* to search for a `*s2' inside `s1' */ + l2--; /* 1st char will be checked by `memchr' */ + l1 = l1-l2; /* `s2' cannot be found after that */ + while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { + init++; /* 1st char is already checked */ + if (memcmp(init, s2+1, l2) == 0) + return init-1; + else { /* correct `l1' and `s1' to try again */ + l1 -= init-s1; + s1 = init; + } + } + return NULL; /* not found */ + } +} + + +static void push_onecapture (MatchState *ms, int i, const char *s, + const char *e) { + if (i >= ms->level) { + if (i == 0) /* ms->level == 0, too */ + lua_pushlstring(ms->L, s, e - s); /* add whole match */ + else + luaL_error(ms->L, "invalid capture index"); + } + else { + ptrdiff_t l = ms->capture[i].len; + if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture"); + if (l == CAP_POSITION) + lua_pushinteger(ms->L, ms->capture[i].init - ms->src_init + 1); + else + lua_pushlstring(ms->L, ms->capture[i].init, l); + } +} + + +static int push_captures (MatchState *ms, const char *s, const char *e) { + int i; + int nlevels = (ms->level == 0 && s) ? 1 : ms->level; + luaL_checkstack(ms->L, nlevels, "too many captures"); + for (i = 0; i < nlevels; i++) + push_onecapture(ms, i, s, e); + return nlevels; /* number of strings pushed */ +} + + +static int str_find_aux (lua_State *L, int find) { + size_t l1, l2; + const char *s = luaL_checklstring(L, 1, &l1); + const char *p = luaL_checklstring(L, 2, &l2); + ptrdiff_t init = posrelat(luaL_optinteger(L, 3, 1), l1) - 1; + if (init < 0) init = 0; + else if ((size_t)(init) > l1) init = (ptrdiff_t)l1; + if (find && (lua_toboolean(L, 4) || /* explicit request? */ + strpbrk(p, SPECIALS) == NULL)) { /* or no special characters? */ + /* do a plain search */ + const char *s2 = lmemfind(s+init, l1-init, p, l2); + if (s2) { + lua_pushinteger(L, s2-s+1); + lua_pushinteger(L, s2-s+l2); + return 2; + } + } + else { + MatchState ms; + int anchor = (*p == '^') ? (p++, 1) : 0; + const char *s1=s+init; + ms.L = L; + ms.src_init = s; + ms.src_end = s+l1; + do { + const char *res; + ms.level = 0; + if ((res=match(&ms, s1, p)) != NULL) { + if (find) { + lua_pushinteger(L, s1-s+1); /* start */ + lua_pushinteger(L, res-s); /* end */ + return push_captures(&ms, NULL, 0) + 2; + } + else + return push_captures(&ms, s1, res); + } + } while (s1++ < ms.src_end && !anchor); + } + lua_pushnil(L); /* not found */ + return 1; +} + + +static int str_find (lua_State *L) { + return str_find_aux(L, 1); +} + + +static int str_match (lua_State *L) { + return str_find_aux(L, 0); +} + + +static int gmatch_aux (lua_State *L) { + MatchState ms; + size_t ls; + const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls); + const char *p = lua_tostring(L, lua_upvalueindex(2)); + const char *src; + ms.L = L; + ms.src_init = s; + ms.src_end = s+ls; + for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); + src <= ms.src_end; + src++) { + const char *e; + ms.level = 0; + if ((e = match(&ms, src, p)) != NULL) { + lua_Integer newstart = e-s; + if (e == src) newstart++; /* empty match? go at least one position */ + lua_pushinteger(L, newstart); + lua_replace(L, lua_upvalueindex(3)); + return push_captures(&ms, src, e); + } + } + return 0; /* not found */ +} + + +static int gmatch (lua_State *L) { + luaL_checkstring(L, 1); + luaL_checkstring(L, 2); + lua_settop(L, 2); + lua_pushinteger(L, 0); + lua_pushcclosure(L, gmatch_aux, 3); + return 1; +} + + +static int gfind_nodef (lua_State *L) { + return luaL_error(L, LUA_QL("string.gfind") " was renamed to " + LUA_QL("string.gmatch")); +} + + +static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, + const char *e) { + size_t l, i; + const char *news = lua_tolstring(ms->L, 3, &l); + for (i = 0; i < l; i++) { + if (news[i] != L_ESC) + luaL_addchar(b, news[i]); + else { + i++; /* skip ESC */ + if (!isdigit(uchar(news[i]))) + luaL_addchar(b, news[i]); + else if (news[i] == '0') + luaL_addlstring(b, s, e - s); + else { + push_onecapture(ms, news[i] - '1', s, e); + luaL_addvalue(b); /* add capture to accumulated result */ + } + } + } +} + + +static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, + const char *e) { + lua_State *L = ms->L; + switch (lua_type(L, 3)) { + case LUA_TNUMBER: + case LUA_TSTRING: { + add_s(ms, b, s, e); + return; + } + case LUA_TFUNCTION: { + int n; + lua_pushvalue(L, 3); + n = push_captures(ms, s, e); + lua_call(L, n, 1); + break; + } + case LUA_TTABLE: { + push_onecapture(ms, 0, s, e); + lua_gettable(L, 3); + break; + } + } + if (!lua_toboolean(L, -1)) { /* nil or false? */ + lua_pop(L, 1); + lua_pushlstring(L, s, e - s); /* keep original text */ + } + else if (!lua_isstring(L, -1)) + luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1)); + luaL_addvalue(b); /* add result to accumulator */ +} + + +static int str_gsub (lua_State *L) { + size_t srcl; + const char *src = luaL_checklstring(L, 1, &srcl); + const char *p = luaL_checkstring(L, 2); + int tr = lua_type(L, 3); + int max_s = luaL_optint(L, 4, srcl+1); + int anchor = (*p == '^') ? (p++, 1) : 0; + int n = 0; + MatchState ms; + luaL_Buffer b; + luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || + tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, + "string/function/table expected"); + luaL_buffinit(L, &b); + ms.L = L; + ms.src_init = src; + ms.src_end = src+srcl; + while (n < max_s) { + const char *e; + ms.level = 0; + e = match(&ms, src, p); + if (e) { + n++; + add_value(&ms, &b, src, e); + } + if (e && e>src) /* non empty match? */ + src = e; /* skip it */ + else if (src < ms.src_end) + luaL_addchar(&b, *src++); + else break; + if (anchor) break; + } + luaL_addlstring(&b, src, ms.src_end-src); + luaL_pushresult(&b); + lua_pushinteger(L, n); /* number of substitutions */ + return 2; +} + +/* }====================================================== */ + + +/* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ +#define MAX_ITEM 512 +/* valid flags in a format specification */ +#define FLAGS "-+ #0" +/* +** maximum size of each format specification (such as '%-099.99d') +** (+10 accounts for %99.99x plus margin of error) +*/ +#define MAX_FORMAT (sizeof(FLAGS) + sizeof(LUA_INTFRMLEN) + 10) + + +static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { + size_t l; + const char *s = luaL_checklstring(L, arg, &l); + luaL_addchar(b, '"'); + while (l--) { + switch (*s) { + case '"': case '\\': case '\n': { + luaL_addchar(b, '\\'); + luaL_addchar(b, *s); + break; + } + case '\r': { + luaL_addlstring(b, "\\r", 2); + break; + } + case '\0': { + luaL_addlstring(b, "\\000", 4); + break; + } + default: { + luaL_addchar(b, *s); + break; + } + } + s++; + } + luaL_addchar(b, '"'); +} + +static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { + const char *p = strfrmt; + while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ + if ((size_t)(p - strfrmt) >= sizeof(FLAGS)) + luaL_error(L, "invalid format (repeated flags)"); + if (isdigit(uchar(*p))) p++; /* skip width */ + if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ + if (*p == '.') { + p++; + if (isdigit(uchar(*p))) p++; /* skip precision */ + if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ + } + if (isdigit(uchar(*p))) + luaL_error(L, "invalid format (width or precision too long)"); + *(form++) = '%'; + strncpy(form, strfrmt, p - strfrmt + 1); + form += p - strfrmt + 1; + *form = '\0'; + return p; +} + + +static void addintlen (char *form) { + size_t l = strlen(form); + char spec = form[l - 1]; + strcpy(form + l - 1, LUA_INTFRMLEN); + form[l + sizeof(LUA_INTFRMLEN) - 2] = spec; + form[l + sizeof(LUA_INTFRMLEN) - 1] = '\0'; +} + + +static int str_format (lua_State *L) { + int arg = 1; + size_t sfl; + const char *strfrmt = luaL_checklstring(L, arg, &sfl); + const char *strfrmt_end = strfrmt+sfl; + luaL_Buffer b; + luaL_buffinit(L, &b); + while (strfrmt < strfrmt_end) { + if (*strfrmt != L_ESC) + luaL_addchar(&b, *strfrmt++); + else if (*++strfrmt == L_ESC) + luaL_addchar(&b, *strfrmt++); /* %% */ + else { /* format item */ + char form[MAX_FORMAT]; /* to store the format (`%...') */ + char buff[MAX_ITEM]; /* to store the formatted item */ + arg++; + strfrmt = scanformat(L, strfrmt, form); + switch (*strfrmt++) { + case 'c': { + sprintf(buff, form, (int)luaL_checknumber(L, arg)); + break; + } + case 'd': case 'i': { + addintlen(form); + sprintf(buff, form, (LUA_INTFRM_T)luaL_checknumber(L, arg)); + break; + } + case 'o': case 'u': case 'x': case 'X': { + addintlen(form); + sprintf(buff, form, (unsigned LUA_INTFRM_T)luaL_checknumber(L, arg)); + break; + } + case 'e': case 'E': case 'f': + case 'g': case 'G': { + sprintf(buff, form, (double)luaL_checknumber(L, arg)); + break; + } + case 'q': { + addquoted(L, &b, arg); + continue; /* skip the 'addsize' at the end */ + } + case 's': { + size_t l; + const char *s = luaL_checklstring(L, arg, &l); + if (!strchr(form, '.') && l >= 100) { + /* no precision and string is too long to be formatted; + keep original string */ + lua_pushvalue(L, arg); + luaL_addvalue(&b); + continue; /* skip the `addsize' at the end */ + } + else { + sprintf(buff, form, s); + break; + } + } + default: { /* also treat cases `pnLlh' */ + return luaL_error(L, "invalid option " LUA_QL("%%%c") " to " + LUA_QL("format"), *(strfrmt - 1)); + } + } + luaL_addlstring(&b, buff, strlen(buff)); + } + } + luaL_pushresult(&b); + return 1; +} + + +static const luaL_Reg strlib[] = { + {"byte", str_byte}, + {"char", str_char}, + {"dump", str_dump}, + {"find", str_find}, + {"format", str_format}, + {"gfind", gfind_nodef}, + {"gmatch", gmatch}, + {"gsub", str_gsub}, + {"len", str_len}, + {"lower", str_lower}, + {"match", str_match}, + {"rep", str_rep}, + {"reverse", str_reverse}, + {"sub", str_sub}, + {"upper", str_upper}, + {NULL, NULL} +}; + + +static void createmetatable (lua_State *L) { + lua_createtable(L, 0, 1); /* create metatable for strings */ + lua_pushliteral(L, ""); /* dummy string */ + lua_pushvalue(L, -2); + lua_setmetatable(L, -2); /* set string metatable */ + lua_pop(L, 1); /* pop dummy string */ + lua_pushvalue(L, -2); /* string library... */ + lua_setfield(L, -2, "__index"); /* ...is the __index metamethod */ + lua_pop(L, 1); /* pop metatable */ +} + + +/* +** Open string library +*/ +LUALIB_API int luaopen_string (lua_State *L) { + luaL_register(L, LUA_STRLIBNAME, strlib); +#if defined(LUA_COMPAT_GFIND) + lua_getfield(L, -1, "gmatch"); + lua_setfield(L, -2, "gfind"); +#endif + createmetatable(L); + return 1; +} + diff --git a/engines/sword25/util/lua/ltable.cpp b/engines/sword25/util/lua/ltable.cpp new file mode 100644 index 0000000000..b2ec0e912a --- /dev/null +++ b/engines/sword25/util/lua/ltable.cpp @@ -0,0 +1,593 @@ +/* +** $Id$ +** Lua tables (hash) +** See Copyright Notice in lua.h +*/ + + +/* +** Implementation of tables (aka arrays, objects, or hash tables). +** Tables keep its elements in two parts: an array part and a hash part. +** Non-negative integer keys are all candidates to be kept in the array +** part. The actual size of the array is the largest `n' such that at +** least half the slots between 0 and n are in use. +** Hash uses a mix of chained scatter table with Brent's variation. +** A main invariant of these tables is that, if an element is not +** in its main position (i.e. the `original' position that its hash gives +** to it), then the colliding element is in its own main position. +** Hence even when the load factor reaches 100%, performance remains good. +*/ + +#include <math.h> +#include <string.h> + +#define ltable_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lgc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstate.h" +#include "ltable.h" + + +/* +** max size of array part is 2^MAXBITS +*/ +#if LUAI_BITSINT > 26 +#define MAXBITS 26 +#else +#define MAXBITS (LUAI_BITSINT-2) +#endif + +#define MAXASIZE (1 << MAXBITS) + + +#define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) + +#define hashstr(t,str) hashpow2(t, (str)->tsv.hash) +#define hashboolean(t,p) hashpow2(t, p) + + +/* +** for some types, it is better to avoid modulus by power of 2, as +** they tend to have many 2 factors. +*/ +#define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) + + +#define hashpointer(t,p) hashmod(t, IntPoint(p)) + + +/* +** number of ints inside a lua_Number +*/ +#define numints cast_int(sizeof(lua_Number)/sizeof(int)) + + + +#define dummynode (&dummynode_) + +static const Node dummynode_ = { + {{NULL}, LUA_TNIL}, /* value */ + {{{NULL}, LUA_TNIL, NULL}} /* key */ +}; + + +/* +** hash for lua_Numbers +*/ +static Node *hashnum (const Table *t, lua_Number n) { + unsigned int a[numints]; + int i; + if (luai_numeq(n, 0)) /* avoid problems with -0 */ + return gnode(t, 0); + memcpy(a, &n, sizeof(a)); + for (i = 1; i < numints; i++) a[0] += a[i]; + return hashmod(t, a[0]); +} + + + +/* +** returns the `main' position of an element in a table (that is, the index +** of its hash value) +*/ +static Node *mainposition (const Table *t, const TValue *key) { + switch (ttype(key)) { + case LUA_TNUMBER: + return hashnum(t, nvalue(key)); + case LUA_TSTRING: + return hashstr(t, rawtsvalue(key)); + case LUA_TBOOLEAN: + return hashboolean(t, bvalue(key)); + case LUA_TLIGHTUSERDATA: + return hashpointer(t, pvalue(key)); + default: + return hashpointer(t, gcvalue(key)); + } +} + + +/* +** returns the index for `key' if `key' is an appropriate key to live in +** the array part of the table, -1 otherwise. +*/ +static int arrayindex (const TValue *key) { + if (ttisnumber(key)) { + lua_Number n = nvalue(key); + int k; + lua_number2int(k, n); + if (luai_numeq(cast_num(k), n)) + return k; + } + return -1; /* `key' did not match some condition */ +} + + +/* +** returns the index of a `key' for table traversals. First goes all +** elements in the array part, then elements in the hash part. The +** beginning of a traversal is signalled by -1. +*/ +static int findindex (lua_State *L, Table *t, StkId key) { + int i; + if (ttisnil(key)) return -1; /* first iteration */ + i = arrayindex(key); + if (0 < i && i <= t->sizearray) /* is `key' inside array part? */ + return i-1; /* yes; that's the index (corrected to C) */ + else { + Node *n = mainposition(t, key); + do { /* check whether `key' is somewhere in the chain */ + /* key may be dead already, but it is ok to use it in `next' */ + if (luaO_rawequalObj(key2tval(n), key) || + (ttype(gkey(n)) == LUA_TDEADKEY && iscollectable(key) && + gcvalue(gkey(n)) == gcvalue(key))) { + i = cast_int(n - gnode(t, 0)); /* key index in hash table */ + /* hash elements are numbered after array ones */ + return i + t->sizearray; + } + else n = gnext(n); + } while (n); + luaG_runerror(L, "invalid key to " LUA_QL("next")); /* key not found */ + return 0; /* to avoid warnings */ + } +} + + +int luaH_next (lua_State *L, Table *t, StkId key) { + int i = findindex(L, t, key); /* find original element */ + for (i++; i < t->sizearray; i++) { /* try first array part */ + if (!ttisnil(&t->array[i])) { /* a non-nil value? */ + setnvalue(key, cast_num(i+1)); + setobj2s(L, key+1, &t->array[i]); + return 1; + } + } + for (i -= t->sizearray; i < sizenode(t); i++) { /* then hash part */ + if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */ + setobj2s(L, key, key2tval(gnode(t, i))); + setobj2s(L, key+1, gval(gnode(t, i))); + return 1; + } + } + return 0; /* no more elements */ +} + + +/* +** {============================================================= +** Rehash +** ============================================================== +*/ + + +static int computesizes (int nums[], int *narray) { + int i; + int twotoi; /* 2^i */ + int a = 0; /* number of elements smaller than 2^i */ + int na = 0; /* number of elements to go to array part */ + int n = 0; /* optimal size for array part */ + for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) { + if (nums[i] > 0) { + a += nums[i]; + if (a > twotoi/2) { /* more than half elements present? */ + n = twotoi; /* optimal size (till now) */ + na = a; /* all elements smaller than n will go to array part */ + } + } + if (a == *narray) break; /* all elements already counted */ + } + *narray = n; + lua_assert(*narray/2 <= na && na <= *narray); + return na; +} + + +static int countint (const TValue *key, int *nums) { + int k = arrayindex(key); + if (0 < k && k <= MAXASIZE) { /* is `key' an appropriate array index? */ + nums[ceillog2(k)]++; /* count as such */ + return 1; + } + else + return 0; +} + + +static int numusearray (const Table *t, int *nums) { + int lg; + int ttlg; /* 2^lg */ + int ause = 0; /* summation of `nums' */ + int i = 1; /* count to traverse all array keys */ + for (lg=0, ttlg=1; lg<=MAXBITS; lg++, ttlg*=2) { /* for each slice */ + int lc = 0; /* counter */ + int lim = ttlg; + if (lim > t->sizearray) { + lim = t->sizearray; /* adjust upper limit */ + if (i > lim) + break; /* no more elements to count */ + } + /* count elements in range (2^(lg-1), 2^lg] */ + for (; i <= lim; i++) { + if (!ttisnil(&t->array[i-1])) + lc++; + } + nums[lg] += lc; + ause += lc; + } + return ause; +} + + +static int numusehash (const Table *t, int *nums, int *pnasize) { + int totaluse = 0; /* total number of elements */ + int ause = 0; /* summation of `nums' */ + int i = sizenode(t); + while (i--) { + Node *n = &t->node[i]; + if (!ttisnil(gval(n))) { + ause += countint(key2tval(n), nums); + totaluse++; + } + } + *pnasize += ause; + return totaluse; +} + + +static void setarrayvector (lua_State *L, Table *t, int size) { + int i; + luaM_reallocvector(L, t->array, t->sizearray, size, TValue); + for (i=t->sizearray; i<size; i++) + setnilvalue(&t->array[i]); + t->sizearray = size; +} + + +static void setnodevector (lua_State *L, Table *t, int size) { + int lsize; + if (size == 0) { /* no elements to hash part? */ + // FIXME: Get rid of const_cast + t->node = const_cast<Node *>(dummynode); /* use common `dummynode' */ + lsize = 0; + } + else { + int i; + lsize = ceillog2(size); + if (lsize > MAXBITS) + luaG_runerror(L, "table overflow"); + size = twoto(lsize); + t->node = luaM_newvector(L, size, Node); + for (i=0; i<size; i++) { + Node *n = gnode(t, i); + gnext(n) = NULL; + setnilvalue(gkey(n)); + setnilvalue(gval(n)); + } + } + t->lsizenode = cast_byte(lsize); + t->lastfree = gnode(t, size); /* all positions are free */ +} + + +static void resize (lua_State *L, Table *t, int nasize, int nhsize) { + int i; + int oldasize = t->sizearray; + int oldhsize = t->lsizenode; + Node *nold = t->node; /* save old hash ... */ + if (nasize > oldasize) /* array part must grow? */ + setarrayvector(L, t, nasize); + /* create new hash part with appropriate size */ + setnodevector(L, t, nhsize); + if (nasize < oldasize) { /* array part must shrink? */ + t->sizearray = nasize; + /* re-insert elements from vanishing slice */ + for (i=nasize; i<oldasize; i++) { + if (!ttisnil(&t->array[i])) + setobjt2t(L, luaH_setnum(L, t, i+1), &t->array[i]); + } + /* shrink array */ + luaM_reallocvector(L, t->array, oldasize, nasize, TValue); + } + /* re-insert elements from hash part */ + for (i = twoto(oldhsize) - 1; i >= 0; i--) { + Node *old = nold+i; + if (!ttisnil(gval(old))) + setobjt2t(L, luaH_set(L, t, key2tval(old)), gval(old)); + } + if (nold != dummynode) + luaM_freearray(L, nold, twoto(oldhsize), Node); /* free old array */ +} + + +void luaH_resizearray (lua_State *L, Table *t, int nasize) { + int nsize = (t->node == dummynode) ? 0 : sizenode(t); + resize(L, t, nasize, nsize); +} + + +static void rehash (lua_State *L, Table *t, const TValue *ek) { + int nasize, na; + int nums[MAXBITS+1]; /* nums[i] = number of keys between 2^(i-1) and 2^i */ + int i; + int totaluse; + for (i=0; i<=MAXBITS; i++) nums[i] = 0; /* reset counts */ + nasize = numusearray(t, nums); /* count keys in array part */ + totaluse = nasize; /* all those keys are integer keys */ + totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */ + /* count extra key */ + nasize += countint(ek, nums); + totaluse++; + /* compute new size for array part */ + na = computesizes(nums, &nasize); + /* resize the table to new computed sizes */ + resize(L, t, nasize, totaluse - na); +} + + + +/* +** }============================================================= +*/ + + +Table *luaH_new (lua_State *L, int narray, int nhash) { + Table *t = luaM_new(L, Table); + luaC_link(L, obj2gco(t), LUA_TTABLE); + t->metatable = NULL; + t->flags = cast_byte(~0); + /* temporary values (kept only if some malloc fails) */ + t->array = NULL; + t->sizearray = 0; + t->lsizenode = 0; + // FIXME: Get rid of const_cast + t->node = const_cast<Node *>(dummynode); + setarrayvector(L, t, narray); + setnodevector(L, t, nhash); + return t; +} + + +void luaH_free (lua_State *L, Table *t) { + if (t->node != dummynode) + luaM_freearray(L, t->node, sizenode(t), Node); + luaM_freearray(L, t->array, t->sizearray, TValue); + luaM_free(L, t); +} + + +static Node *getfreepos (Table *t) { + while (t->lastfree-- > t->node) { + if (ttisnil(gkey(t->lastfree))) + return t->lastfree; + } + return NULL; /* could not find a free place */ +} + + + +/* +** inserts a new key into a hash table; first, check whether key's main +** position is free. If not, check whether colliding node is in its main +** position or not: if it is not, move colliding node to an empty place and +** put new key in its main position; otherwise (colliding node is in its main +** position), new key goes to an empty position. +*/ +static TValue *newkey (lua_State *L, Table *t, const TValue *key) { + Node *mp = mainposition(t, key); + if (!ttisnil(gval(mp)) || mp == dummynode) { + Node *othern; + Node *n = getfreepos(t); /* get a free place */ + if (n == NULL) { /* cannot find a free place? */ + rehash(L, t, key); /* grow table */ + return luaH_set(L, t, key); /* re-insert key into grown table */ + } + lua_assert(n != dummynode); + othern = mainposition(t, key2tval(mp)); + if (othern != mp) { /* is colliding node out of its main position? */ + /* yes; move colliding node into free position */ + while (gnext(othern) != mp) othern = gnext(othern); /* find previous */ + gnext(othern) = n; /* redo the chain with `n' in place of `mp' */ + *n = *mp; /* copy colliding node into free pos. (mp->next also goes) */ + gnext(mp) = NULL; /* now `mp' is free */ + setnilvalue(gval(mp)); + } + else { /* colliding node is in its own main position */ + /* new node will go into free position */ + gnext(n) = gnext(mp); /* chain new position */ + gnext(mp) = n; + mp = n; + } + } + gkey(mp)->value = key->value; gkey(mp)->tt = key->tt; + luaC_barriert(L, t, key); + lua_assert(ttisnil(gval(mp))); + return gval(mp); +} + + +/* +** search function for integers +*/ +const TValue *luaH_getnum (Table *t, int key) { + /* (1 <= key && key <= t->sizearray) */ + if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray)) + return &t->array[key-1]; + else { + lua_Number nk = cast_num(key); + Node *n = hashnum(t, nk); + do { /* check whether `key' is somewhere in the chain */ + if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk)) + return gval(n); /* that's it */ + else n = gnext(n); + } while (n); + return luaO_nilobject; + } +} + + +/* +** search function for strings +*/ +const TValue *luaH_getstr (Table *t, TString *key) { + Node *n = hashstr(t, key); + do { /* check whether `key' is somewhere in the chain */ + if (ttisstring(gkey(n)) && rawtsvalue(gkey(n)) == key) + return gval(n); /* that's it */ + else n = gnext(n); + } while (n); + return luaO_nilobject; +} + + +/* +** main search function +*/ +const TValue *luaH_get (Table *t, const TValue *key) { + switch (ttype(key)) { + case LUA_TNIL: return luaO_nilobject; + case LUA_TSTRING: return luaH_getstr(t, rawtsvalue(key)); + case LUA_TNUMBER: { + int k; + lua_Number n = nvalue(key); + lua_number2int(k, n); + if (luai_numeq(cast_num(k), nvalue(key))) /* index is int? */ + return luaH_getnum(t, k); /* use specialized version */ + /* else go through */ + } + default: { + Node *n = mainposition(t, key); + do { /* check whether `key' is somewhere in the chain */ + if (luaO_rawequalObj(key2tval(n), key)) + return gval(n); /* that's it */ + else n = gnext(n); + } while (n); + return luaO_nilobject; + } + } +} + + +TValue *luaH_set (lua_State *L, Table *t, const TValue *key) { + const TValue *p = luaH_get(t, key); + t->flags = 0; + if (p != luaO_nilobject) + // FIXME: Get rid of const_cast + return const_cast<TValue *>(p); + else { + if (ttisnil(key)) luaG_runerror(L, "table index is nil"); + else if (ttisnumber(key) && luai_numisnan(nvalue(key))) + luaG_runerror(L, "table index is NaN"); + return newkey(L, t, key); + } +} + + +TValue *luaH_setnum (lua_State *L, Table *t, int key) { + const TValue *p = luaH_getnum(t, key); + if (p != luaO_nilobject) + // FIXME: Get rid of const_cast + return const_cast<TValue *>(p); + else { + TValue k; + setnvalue(&k, cast_num(key)); + return newkey(L, t, &k); + } +} + + +TValue *luaH_setstr (lua_State *L, Table *t, TString *key) { + const TValue *p = luaH_getstr(t, key); + if (p != luaO_nilobject) + // FIXME: Get rid of const_cast + return const_cast<TValue *>(p); + else { + TValue k; + setsvalue(L, &k, key); + return newkey(L, t, &k); + } +} + + +static int unbound_search (Table *t, unsigned int j) { + unsigned int i = j; /* i is zero or a present index */ + j++; + /* find `i' and `j' such that i is present and j is not */ + while (!ttisnil(luaH_getnum(t, j))) { + i = j; + j *= 2; + if (j > cast(unsigned int, MAX_INT)) { /* overflow? */ + /* table was built with bad purposes: resort to linear search */ + i = 1; + while (!ttisnil(luaH_getnum(t, i))) i++; + return i - 1; + } + } + /* now do a binary search between them */ + while (j - i > 1) { + unsigned int m = (i+j)/2; + if (ttisnil(luaH_getnum(t, m))) j = m; + else i = m; + } + return i; +} + + +/* +** Try to find a boundary in table `t'. A `boundary' is an integer index +** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). +*/ +int luaH_getn (Table *t) { + unsigned int j = t->sizearray; + if (j > 0 && ttisnil(&t->array[j - 1])) { + /* there is a boundary in the array part: (binary) search for it */ + unsigned int i = 0; + while (j - i > 1) { + unsigned int m = (i+j)/2; + if (ttisnil(&t->array[m - 1])) j = m; + else i = m; + } + return i; + } + /* else must find a boundary in hash part */ + else if (t->node == dummynode) /* hash part is empty? */ + return j; /* that is easy... */ + else return unbound_search(t, j); +} + + + +#if defined(LUA_DEBUG) + +Node *luaH_mainposition (const Table *t, const TValue *key) { + return mainposition(t, key); +} + +int luaH_isdummy (Node *n) { return n == dummynode; } + +#endif diff --git a/engines/sword25/util/lua/ltable.h b/engines/sword25/util/lua/ltable.h new file mode 100644 index 0000000000..aa28914871 --- /dev/null +++ b/engines/sword25/util/lua/ltable.h @@ -0,0 +1,40 @@ +/* +** $Id$ +** Lua tables (hash) +** See Copyright Notice in lua.h +*/ + +#ifndef ltable_h +#define ltable_h + +#include "lobject.h" + + +#define gnode(t,i) (&(t)->node[i]) +#define gkey(n) (&(n)->i_key.nk) +#define gval(n) (&(n)->i_val) +#define gnext(n) ((n)->i_key.nk.next) + +#define key2tval(n) (&(n)->i_key.tvk) + + +LUAI_FUNC const TValue *luaH_getnum (Table *t, int key); +LUAI_FUNC TValue *luaH_setnum (lua_State *L, Table *t, int key); +LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); +LUAI_FUNC TValue *luaH_setstr (lua_State *L, Table *t, TString *key); +LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); +LUAI_FUNC TValue *luaH_set (lua_State *L, Table *t, const TValue *key); +LUAI_FUNC Table *luaH_new (lua_State *L, int narray, int lnhash); +LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, int nasize); +LUAI_FUNC void luaH_free (lua_State *L, Table *t); +LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); +LUAI_FUNC int luaH_getn (Table *t); + + +#if defined(LUA_DEBUG) +LUAI_FUNC Node *luaH_mainposition (const Table *t, const TValue *key); +LUAI_FUNC int luaH_isdummy (Node *n); +#endif + + +#endif diff --git a/engines/sword25/util/lua/ltablib.cpp b/engines/sword25/util/lua/ltablib.cpp new file mode 100644 index 0000000000..607c09ae71 --- /dev/null +++ b/engines/sword25/util/lua/ltablib.cpp @@ -0,0 +1,279 @@ +/* +** $Id$ +** Library for Table Manipulation +** See Copyright Notice in lua.h +*/ + + +#include <stddef.h> + +#define ltablib_c +#define LUA_LIB + +#include "lua.h" + +#include "lauxlib.h" +#include "lualib.h" + + +#define aux_getn(L,n) (luaL_checktype(L, n, LUA_TTABLE), luaL_getn(L, n)) + + +static int foreachi (lua_State *L) { + int i; + int n = aux_getn(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + for (i=1; i <= n; i++) { + lua_pushvalue(L, 2); /* function */ + lua_pushinteger(L, i); /* 1st argument */ + lua_rawgeti(L, 1, i); /* 2nd argument */ + lua_call(L, 2, 1); + if (!lua_isnil(L, -1)) + return 1; + lua_pop(L, 1); /* remove nil result */ + } + return 0; +} + + +static int foreach (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checktype(L, 2, LUA_TFUNCTION); + lua_pushnil(L); /* first key */ + while (lua_next(L, 1)) { + lua_pushvalue(L, 2); /* function */ + lua_pushvalue(L, -3); /* key */ + lua_pushvalue(L, -3); /* value */ + lua_call(L, 2, 1); + if (!lua_isnil(L, -1)) + return 1; + lua_pop(L, 2); /* remove value and result */ + } + return 0; +} + + +static int maxn (lua_State *L) { + lua_Number max = 0; + luaL_checktype(L, 1, LUA_TTABLE); + lua_pushnil(L); /* first key */ + while (lua_next(L, 1)) { + lua_pop(L, 1); /* remove value */ + if (lua_type(L, -1) == LUA_TNUMBER) { + lua_Number v = lua_tonumber(L, -1); + if (v > max) max = v; + } + } + lua_pushnumber(L, max); + return 1; +} + + +static int getn (lua_State *L) { + lua_pushinteger(L, aux_getn(L, 1)); + return 1; +} + + +static int setn (lua_State *L) { + luaL_checktype(L, 1, LUA_TTABLE); +#ifndef luaL_setn + luaL_setn(L, 1, luaL_checkint(L, 2)); +#else + luaL_error(L, LUA_QL("setn") " is obsolete"); +#endif + lua_pushvalue(L, 1); + return 1; +} + + +static int tinsert (lua_State *L) { + int e = aux_getn(L, 1) + 1; /* first empty element */ + int pos; /* where to insert new element */ + switch (lua_gettop(L)) { + case 2: { /* called with only 2 arguments */ + pos = e; /* insert new element at the end */ + break; + } + case 3: { + int i; + pos = luaL_checkint(L, 2); /* 2nd argument is the position */ + if (pos > e) e = pos; /* `grow' array if necessary */ + for (i = e; i > pos; i--) { /* move up elements */ + lua_rawgeti(L, 1, i-1); + lua_rawseti(L, 1, i); /* t[i] = t[i-1] */ + } + break; + } + default: { + return luaL_error(L, "wrong number of arguments to " LUA_QL("insert")); + } + } + luaL_setn(L, 1, e); /* new size */ + lua_rawseti(L, 1, pos); /* t[pos] = v */ + return 0; +} + + +static int tremove (lua_State *L) { + int e = aux_getn(L, 1); + int pos = luaL_optint(L, 2, e); + if (!(1 <= pos && pos <= e)) /* position is outside bounds? */ + return 0; /* nothing to remove */ + luaL_setn(L, 1, e - 1); /* t.n = n-1 */ + lua_rawgeti(L, 1, pos); /* result = t[pos] */ + for ( ;pos<e; pos++) { + lua_rawgeti(L, 1, pos+1); + lua_rawseti(L, 1, pos); /* t[pos] = t[pos+1] */ + } + lua_pushnil(L); + lua_rawseti(L, 1, e); /* t[e] = nil */ + return 1; +} + + +static int tconcat (lua_State *L) { + luaL_Buffer b; + size_t lsep; + int i, last; + const char *sep = luaL_optlstring(L, 2, "", &lsep); + luaL_checktype(L, 1, LUA_TTABLE); + i = luaL_optint(L, 3, 1); + last = luaL_opt(L, luaL_checkint, 4, luaL_getn(L, 1)); + luaL_buffinit(L, &b); + for (; i <= last; i++) { + lua_rawgeti(L, 1, i); + luaL_argcheck(L, lua_isstring(L, -1), 1, "table contains non-strings"); + luaL_addvalue(&b); + if (i != last) + luaL_addlstring(&b, sep, lsep); + } + luaL_pushresult(&b); + return 1; +} + + + +/* +** {====================================================== +** Quicksort +** (based on `Algorithms in MODULA-3', Robert Sedgewick; +** Addison-Wesley, 1993.) +*/ + + +static void set2 (lua_State *L, int i, int j) { + lua_rawseti(L, 1, i); + lua_rawseti(L, 1, j); +} + +static int sort_comp (lua_State *L, int a, int b) { + if (!lua_isnil(L, 2)) { /* function? */ + int res; + lua_pushvalue(L, 2); + lua_pushvalue(L, a-1); /* -1 to compensate function */ + lua_pushvalue(L, b-2); /* -2 to compensate function and `a' */ + lua_call(L, 2, 1); + res = lua_toboolean(L, -1); + lua_pop(L, 1); + return res; + } + else /* a < b? */ + return lua_lessthan(L, a, b); +} + +static void auxsort (lua_State *L, int l, int u) { + while (l < u) { /* for tail recursion */ + int i, j; + /* sort elements a[l], a[(l+u)/2] and a[u] */ + lua_rawgeti(L, 1, l); + lua_rawgeti(L, 1, u); + if (sort_comp(L, -1, -2)) /* a[u] < a[l]? */ + set2(L, l, u); /* swap a[l] - a[u] */ + else + lua_pop(L, 2); + if (u-l == 1) break; /* only 2 elements */ + i = (l+u)/2; + lua_rawgeti(L, 1, i); + lua_rawgeti(L, 1, l); + if (sort_comp(L, -2, -1)) /* a[i]<a[l]? */ + set2(L, i, l); + else { + lua_pop(L, 1); /* remove a[l] */ + lua_rawgeti(L, 1, u); + if (sort_comp(L, -1, -2)) /* a[u]<a[i]? */ + set2(L, i, u); + else + lua_pop(L, 2); + } + if (u-l == 2) break; /* only 3 elements */ + lua_rawgeti(L, 1, i); /* Pivot */ + lua_pushvalue(L, -1); + lua_rawgeti(L, 1, u-1); + set2(L, i, u-1); + /* a[l] <= P == a[u-1] <= a[u], only need to sort from l+1 to u-2 */ + i = l; j = u-1; + for (;;) { /* invariant: a[l..i] <= P <= a[j..u] */ + /* repeat ++i until a[i] >= P */ + while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2)) { + if (i>u) luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[i] */ + } + /* repeat --j until a[j] <= P */ + while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1)) { + if (j<l) luaL_error(L, "invalid order function for sorting"); + lua_pop(L, 1); /* remove a[j] */ + } + if (j<i) { + lua_pop(L, 3); /* pop pivot, a[i], a[j] */ + break; + } + set2(L, i, j); + } + lua_rawgeti(L, 1, u-1); + lua_rawgeti(L, 1, i); + set2(L, u-1, i); /* swap pivot (a[u-1]) with a[i] */ + /* a[l..i-1] <= a[i] == P <= a[i+1..u] */ + /* adjust so that smaller half is in [j..i] and larger one in [l..u] */ + if (i-l < u-i) { + j=l; i=i-1; l=i+2; + } + else { + j=i+1; i=u; u=j-2; + } + auxsort(L, j, i); /* call recursively the smaller one */ + } /* repeat the routine for the larger one */ +} + +static int sort (lua_State *L) { + int n = aux_getn(L, 1); + luaL_checkstack(L, 40, ""); /* assume array is smaller than 2^40 */ + if (!lua_isnoneornil(L, 2)) /* is there a 2nd argument? */ + luaL_checktype(L, 2, LUA_TFUNCTION); + lua_settop(L, 2); /* make sure there is two arguments */ + auxsort(L, 1, n); + return 0; +} + +/* }====================================================== */ + + +static const luaL_Reg tab_funcs[] = { + {"concat", tconcat}, + {"foreach", foreach}, + {"foreachi", foreachi}, + {"getn", getn}, + {"maxn", maxn}, + {"insert", tinsert}, + {"remove", tremove}, + {"setn", setn}, + {"sort", sort}, + {NULL, NULL} +}; + + +LUALIB_API int luaopen_table (lua_State *L) { + luaL_register(L, LUA_TABLIBNAME, tab_funcs); + return 1; +} + diff --git a/engines/sword25/util/lua/ltm.cpp b/engines/sword25/util/lua/ltm.cpp new file mode 100644 index 0000000000..02856a58fc --- /dev/null +++ b/engines/sword25/util/lua/ltm.cpp @@ -0,0 +1,75 @@ +/* +** $Id$ +** Tag methods +** See Copyright Notice in lua.h +*/ + + +#include <string.h> + +#define ltm_c +#define LUA_CORE + +#include "lua.h" + +#include "lobject.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" + + + +const char *const luaT_typenames[] = { + "nil", "boolean", "userdata", "number", + "string", "table", "function", "userdata", "thread", + "proto", "upval" +}; + + +void luaT_init (lua_State *L) { + static const char *const luaT_eventname[] = { /* ORDER TM */ + "__index", "__newindex", + "__gc", "__mode", "__eq", + "__add", "__sub", "__mul", "__div", "__mod", + "__pow", "__unm", "__len", "__lt", "__le", + "__concat", "__call" + }; + int i; + for (i=0; i<TM_N; i++) { + G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]); + luaS_fix(G(L)->tmname[i]); /* never collect these names */ + } +} + + +/* +** function to be used with macro "fasttm": optimized for absence of +** tag methods +*/ +const TValue *luaT_gettm (Table *events, TMS event, TString *ename) { + const TValue *tm = luaH_getstr(events, ename); + lua_assert(event <= TM_EQ); + if (ttisnil(tm)) { /* no tag method? */ + events->flags |= cast_byte(1u<<event); /* cache this fact */ + return NULL; + } + else return tm; +} + + +const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event) { + Table *mt; + switch (ttype(o)) { + case LUA_TTABLE: + mt = hvalue(o)->metatable; + break; + case LUA_TUSERDATA: + mt = uvalue(o)->metatable; + break; + default: + mt = G(L)->mt[ttype(o)]; + } + return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject); +} + diff --git a/engines/sword25/util/lua/ltm.h b/engines/sword25/util/lua/ltm.h new file mode 100644 index 0000000000..1b89683ef3 --- /dev/null +++ b/engines/sword25/util/lua/ltm.h @@ -0,0 +1,54 @@ +/* +** $Id$ +** Tag methods +** See Copyright Notice in lua.h +*/ + +#ifndef ltm_h +#define ltm_h + + +#include "lobject.h" + + +/* +* WARNING: if you change the order of this enumeration, +* grep "ORDER TM" +*/ +typedef enum { + TM_INDEX, + TM_NEWINDEX, + TM_GC, + TM_MODE, + TM_EQ, /* last tag method with `fast' access */ + TM_ADD, + TM_SUB, + TM_MUL, + TM_DIV, + TM_MOD, + TM_POW, + TM_UNM, + TM_LEN, + TM_LT, + TM_LE, + TM_CONCAT, + TM_CALL, + TM_N /* number of elements in the enum */ +} TMS; + + + +#define gfasttm(g,et,e) ((et) == NULL ? NULL : \ + ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e])) + +#define fasttm(l,et,e) gfasttm(G(l), et, e) + +LUAI_DATA const char *const luaT_typenames[]; + + +LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); +LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, + TMS event); +LUAI_FUNC void luaT_init (lua_State *L); + +#endif diff --git a/engines/sword25/util/lua/lua.h b/engines/sword25/util/lua/lua.h new file mode 100644 index 0000000000..088a511cf9 --- /dev/null +++ b/engines/sword25/util/lua/lua.h @@ -0,0 +1,388 @@ +/* +** $Id$ +** Lua - An Extensible Extension Language +** Lua.org, PUC-Rio, Brazil (http://www.lua.org) +** See Copyright Notice at the end of this file +*/ + + +#ifndef lua_h +#define lua_h + +#include <stdarg.h> +#include <stddef.h> + + +#include "luaconf.h" + + +#define LUA_VERSION "Lua 5.1" +#define LUA_RELEASE "Lua 5.1.3" +#define LUA_VERSION_NUM 501 +#define LUA_COPYRIGHT "Copyright (C) 1994-2008 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes" + + +/* mark for precompiled code (`<esc>Lua') */ +#define LUA_SIGNATURE "\033Lua" + +/* option for multiple returns in `lua_pcall' and `lua_call' */ +#define LUA_MULTRET (-1) + + +/* +** pseudo-indices +*/ +#define LUA_REGISTRYINDEX (-10000) +#define LUA_ENVIRONINDEX (-10001) +#define LUA_GLOBALSINDEX (-10002) +#define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i)) + + +/* thread status; 0 is OK */ +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRERR 5 + + +typedef struct lua_State lua_State; + +typedef int (*lua_CFunction) (lua_State *L); + + +/* +** functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud); + + +/* +** prototype for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + + +/* +** basic types +*/ +#define LUA_TNONE (-1) + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + + + +/* minimum Lua stack available to a C function */ +#define LUA_MINSTACK 20 + + +/* +** generic extra include file +*/ +#if defined(LUA_USER_H) +#include LUA_USER_H +#endif + + +/* type of numbers in Lua */ +typedef LUA_NUMBER lua_Number; + + +/* type for integer functions */ +typedef LUA_INTEGER lua_Integer; + + + +/* +** state manipulation +*/ +LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); +LUA_API void (lua_close) (lua_State *L); +LUA_API lua_State *(lua_newthread) (lua_State *L); + +LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); + + +/* +** basic stack manipulation +*/ +LUA_API int (lua_gettop) (lua_State *L); +LUA_API void (lua_settop) (lua_State *L, int idx); +LUA_API void (lua_pushvalue) (lua_State *L, int idx); +LUA_API void (lua_remove) (lua_State *L, int idx); +LUA_API void (lua_insert) (lua_State *L, int idx); +LUA_API void (lua_replace) (lua_State *L, int idx); +LUA_API int (lua_checkstack) (lua_State *L, int sz); + +LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); + + +/* +** access functions (stack -> C) +*/ + +LUA_API int (lua_isnumber) (lua_State *L, int idx); +LUA_API int (lua_isstring) (lua_State *L, int idx); +LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isuserdata) (lua_State *L, int idx); +LUA_API int (lua_type) (lua_State *L, int idx); +LUA_API const char *(lua_typename) (lua_State *L, int tp); + +LUA_API int (lua_equal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_lessthan) (lua_State *L, int idx1, int idx2); + +LUA_API lua_Number (lua_tonumber) (lua_State *L, int idx); +LUA_API lua_Integer (lua_tointeger) (lua_State *L, int idx); +LUA_API int (lua_toboolean) (lua_State *L, int idx); +LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); +LUA_API size_t (lua_objlen) (lua_State *L, int idx); +LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); +LUA_API void *(lua_touserdata) (lua_State *L, int idx); +LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); +LUA_API const void *(lua_topointer) (lua_State *L, int idx); + + +/* +** push functions (C -> stack) +*/ +LUA_API void (lua_pushnil) (lua_State *L); +LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); +LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); +LUA_API void (lua_pushlstring) (lua_State *L, const char *s, size_t l); +LUA_API void (lua_pushstring) (lua_State *L, const char *s); +LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, + va_list argp); +LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); +LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); +LUA_API void (lua_pushboolean) (lua_State *L, int b); +LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); +LUA_API int (lua_pushthread) (lua_State *L); + + +/* +** get functions (Lua -> stack) +*/ +LUA_API void (lua_gettable) (lua_State *L, int idx); +LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_rawget) (lua_State *L, int idx); +LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n); +LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); +LUA_API int (lua_getmetatable) (lua_State *L, int objindex); +LUA_API void (lua_getfenv) (lua_State *L, int idx); + + +/* +** set functions (stack -> Lua) +*/ +LUA_API void (lua_settable) (lua_State *L, int idx); +LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_rawset) (lua_State *L, int idx); +LUA_API void (lua_rawseti) (lua_State *L, int idx, int n); +LUA_API int (lua_setmetatable) (lua_State *L, int objindex); +LUA_API int (lua_setfenv) (lua_State *L, int idx); + + +/* +** `load' and `call' functions (load and run Lua code) +*/ +LUA_API void (lua_call) (lua_State *L, int nargs, int nresults); +LUA_API int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc); +LUA_API int (lua_cpcall) (lua_State *L, lua_CFunction func, void *ud); +LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, + const char *chunkname); + +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data); + + +/* +** coroutine functions +*/ +LUA_API int (lua_yield) (lua_State *L, int nresults); +LUA_API int (lua_resume) (lua_State *L, int narg); +LUA_API int (lua_status) (lua_State *L); + +/* +** garbage-collection function and options +*/ + +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 + +LUA_API int (lua_gc) (lua_State *L, int what, int data); + + +/* +** miscellaneous functions +*/ + +LUA_API int (lua_error) (lua_State *L); + +LUA_API int (lua_next) (lua_State *L, int idx); + +LUA_API void (lua_concat) (lua_State *L, int n); + +LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); +LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); + + + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + +#define lua_pop(L,n) lua_settop(L, -(n)-1) + +#define lua_newtable(L) lua_createtable(L, 0, 0) + +#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) + +#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) + +#define lua_strlen(L,i) lua_objlen(L, (i)) + +#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) + +#define lua_pushliteral(L, s) \ + lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1) + +#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s)) +#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s)) + +#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) + + + +/* +** compatibility macros and functions +*/ + +#define lua_open() luaL_newstate() + +#define lua_getregistry(L) lua_pushvalue(L, LUA_REGISTRYINDEX) + +#define lua_getgccount(L) lua_gc(L, LUA_GCCOUNT, 0) + +#define lua_Chunkreader lua_Reader +#define lua_Chunkwriter lua_Writer + + +/* hack */ +LUA_API void lua_setlevel (lua_State *from, lua_State *to); + + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + + +/* +** Event codes +*/ +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 +#define LUA_HOOKTAILRET 4 + + +/* +** Event masks +*/ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) + +typedef struct lua_Debug lua_Debug; /* activation record */ + + +/* Functions to be called by the debuger in specific events */ +typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); + + +LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar); +LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n); +LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n); + +LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook lua_gethook (lua_State *L); +LUA_API int lua_gethookmask (lua_State *L); +LUA_API int lua_gethookcount (lua_State *L); + + +struct lua_Debug { + int event; + const char *name; /* (n) */ + const char *namewhat; /* (n) `global', `local', `field', `method' */ + const char *what; /* (S) `Lua', `C', `main', `tail' */ + const char *source; /* (S) */ + int currentline; /* (l) */ + int nups; /* (u) number of upvalues */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + int i_ci; /* active function */ +}; + +/* }====================================================================== */ + + +/****************************************************************************** +* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +* +* 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. +******************************************************************************/ + + +#endif diff --git a/engines/sword25/util/lua/luaconf.h b/engines/sword25/util/lua/luaconf.h new file mode 100644 index 0000000000..fa565c7697 --- /dev/null +++ b/engines/sword25/util/lua/luaconf.h @@ -0,0 +1,763 @@ +/* +** $Id$ +** Configuration file for Lua +** See Copyright Notice in lua.h +*/ + + +#ifndef lconfig_h +#define lconfig_h + +#include <limits.h> +#include <stddef.h> + + +/* +** ================================================================== +** Search for "@@" to find all configurable definitions. +** =================================================================== +*/ + + +/* +@@ LUA_ANSI controls the use of non-ansi features. +** CHANGE it (define it) if you want Lua to avoid the use of any +** non-ansi feature or library. +*/ +#if defined(__STRICT_ANSI__) +#define LUA_ANSI +#endif + + +#if !defined(LUA_ANSI) && defined(_WIN32) +#define LUA_WIN +#endif + +#if defined(LUA_USE_LINUX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* needs an extra library: -ldl */ +#define LUA_USE_READLINE /* needs some extra libraries */ +#endif + +#if defined(LUA_USE_MACOSX) +#define LUA_USE_POSIX +#define LUA_DL_DYLD /* does not need extra library */ +#endif + + + +/* +@@ LUA_USE_POSIX includes all functionallity listed as X/Open System +@* Interfaces Extension (XSI). +** CHANGE it (define it) if your system is XSI compatible. +*/ +#if defined(LUA_USE_POSIX) +#define LUA_USE_MKSTEMP +#define LUA_USE_ISATTY +#define LUA_USE_POPEN +#define LUA_USE_ULONGJMP +#endif + + +/* +@@ LUA_PATH and LUA_CPATH are the names of the environment variables that +@* Lua check to set its paths. +@@ LUA_INIT is the name of the environment variable that Lua +@* checks for initialization code. +** CHANGE them if you want different names. +*/ +#define LUA_PATH "LUA_PATH" +#define LUA_CPATH "LUA_CPATH" +#define LUA_INIT "LUA_INIT" + + +/* +@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for +@* Lua libraries. +@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for +@* C libraries. +** CHANGE them if your machine has a non-conventional directory +** hierarchy or if you want to install your libraries in +** non-conventional directories. +*/ +#if defined(_WIN32) +/* +** In Windows, any exclamation mark ('!') in the path is replaced by the +** path of the directory of the executable file of the current process. +*/ +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_PATH_DEFAULT \ + ".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua" +#define LUA_CPATH_DEFAULT \ + ".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll" + +#else +#define LUA_ROOT "/usr/local/" +#define LUA_LDIR LUA_ROOT "share/lua/5.1/" +#define LUA_CDIR LUA_ROOT "lib/lua/5.1/" +#define LUA_PATH_DEFAULT \ + "./?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua" +#define LUA_CPATH_DEFAULT \ + "./?.so;" LUA_CDIR"?.so;" LUA_CDIR"loadall.so" +#endif + + +/* +@@ LUA_DIRSEP is the directory separator (for submodules). +** CHANGE it if your machine does not use "/" as the directory separator +** and is not Windows. (On Windows Lua automatically uses "\".) +*/ +#if defined(_WIN32) +#define LUA_DIRSEP "\\" +#else +#define LUA_DIRSEP "/" +#endif + + +/* +@@ LUA_PATHSEP is the character that separates templates in a path. +@@ LUA_PATH_MARK is the string that marks the substitution points in a +@* template. +@@ LUA_EXECDIR in a Windows path is replaced by the executable's +@* directory. +@@ LUA_IGMARK is a mark to ignore all before it when bulding the +@* luaopen_ function name. +** CHANGE them if for some reason your system cannot use those +** characters. (E.g., if one of those characters is a common character +** in file/directory names.) Probably you do not need to change them. +*/ +#define LUA_PATHSEP ";" +#define LUA_PATH_MARK "?" +#define LUA_EXECDIR "!" +#define LUA_IGMARK "-" + + +/* +@@ LUA_INTEGER is the integral type used by lua_pushinteger/lua_tointeger. +** CHANGE that if ptrdiff_t is not adequate on your machine. (On most +** machines, ptrdiff_t gives a good choice between int or long.) +*/ +#define LUA_INTEGER ptrdiff_t + + +/* +@@ LUA_API is a mark for all core API functions. +@@ LUALIB_API is a mark for all standard library functions. +** CHANGE them if you need to define those functions in some special way. +** For instance, if you want to create one Windows DLL with the core and +** the libraries, you may want to use the following definition (define +** LUA_BUILD_AS_DLL to get it). +*/ +#if defined(LUA_BUILD_AS_DLL) + +#if defined(LUA_CORE) || defined(LUA_LIB) +#define LUA_API __declspec(dllexport) +#else +#define LUA_API __declspec(dllimport) +#endif + +#else + +#define LUA_API extern + +#endif + +/* more often than not the libs go together with the core */ +#define LUALIB_API LUA_API + + +/* +@@ LUAI_FUNC is a mark for all extern functions that are not to be +@* exported to outside modules. +@@ LUAI_DATA is a mark for all extern (const) variables that are not to +@* be exported to outside modules. +** CHANGE them if you need to mark them in some special way. Elf/gcc +** (versions 3.2 and later) mark them as "hidden" to optimize access +** when Lua is compiled as a shared library. +*/ +#if defined(luaall_c) +#define LUAI_FUNC static +#define LUAI_DATA /* empty */ + +#elif defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ + defined(__ELF__) +#define LUAI_FUNC __attribute__((visibility("hidden"))) extern +#define LUAI_DATA LUAI_FUNC + +#else +#define LUAI_FUNC extern +#define LUAI_DATA extern +#endif + + + +/* +@@ LUA_QL describes how error messages quote program elements. +** CHANGE it if you want a different appearance. +*/ +#define LUA_QL(x) "'" x "'" +#define LUA_QS LUA_QL("%s") + + +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@* of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 + + +/* +** {================================================================== +** Stand-alone configuration +** =================================================================== +*/ + +#if defined(lua_c) || defined(luaall_c) + +/* +@@ lua_stdin_is_tty detects whether the standard input is a 'tty' (that +@* is, whether we're running lua interactively). +** CHANGE it if you have a better definition for non-POSIX/non-Windows +** systems. +*/ +#if defined(LUA_USE_ISATTY) +#include <unistd.h> +#define lua_stdin_is_tty() isatty(0) +#elif defined(LUA_WIN) +#include <io.h> +#include <stdio.h> +#define lua_stdin_is_tty() _isatty(_fileno(stdin)) +#else +#define lua_stdin_is_tty() 1 /* assume stdin is a tty */ +#endif + + +/* +@@ LUA_PROMPT is the default prompt used by stand-alone Lua. +@@ LUA_PROMPT2 is the default continuation prompt used by stand-alone Lua. +** CHANGE them if you want different prompts. (You can also change the +** prompts dynamically, assigning to globals _PROMPT/_PROMPT2.) +*/ +#define LUA_PROMPT "> " +#define LUA_PROMPT2 ">> " + + +/* +@@ LUA_PROGNAME is the default name for the stand-alone Lua program. +** CHANGE it if your stand-alone interpreter has a different name and +** your system is not able to detect that name automatically. +*/ +#define LUA_PROGNAME "lua" + + +/* +@@ LUA_MAXINPUT is the maximum length for an input line in the +@* stand-alone interpreter. +** CHANGE it if you need longer lines. +*/ +#define LUA_MAXINPUT 512 + + +/* +@@ lua_readline defines how to show a prompt and then read a line from +@* the standard input. +@@ lua_saveline defines how to "save" a read line in a "history". +@@ lua_freeline defines how to free a line read by lua_readline. +** CHANGE them if you want to improve this functionality (e.g., by using +** GNU readline and history facilities). +*/ +#if defined(LUA_USE_READLINE) +#include <stdio.h> +#include <readline/readline.h> +#include <readline/history.h> +#define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL) +#define lua_saveline(L,idx) \ + if (lua_strlen(L,idx) > 0) /* non-empty line? */ \ + add_history(lua_tostring(L, idx)); /* add it to history */ +#define lua_freeline(L,b) ((void)L, free(b)) +#else +#define lua_readline(L,b,p) \ + ((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \ + fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */ +#define lua_saveline(L,idx) { (void)L; (void)idx; } +#define lua_freeline(L,b) { (void)L; (void)b; } +#endif + +#endif + +/* }================================================================== */ + + +/* +@@ LUAI_GCPAUSE defines the default pause between garbage-collector cycles +@* as a percentage. +** CHANGE it if you want the GC to run faster or slower (higher values +** mean larger pauses which mean slower collection.) You can also change +** this value dynamically. +*/ +#define LUAI_GCPAUSE 200 /* 200% (wait memory to double before next GC) */ + + +/* +@@ LUAI_GCMUL defines the default speed of garbage collection relative to +@* memory allocation as a percentage. +** CHANGE it if you want to change the granularity of the garbage +** collection. (Higher values mean coarser collections. 0 represents +** infinity, where each step performs a full collection.) You can also +** change this value dynamically. +*/ +#define LUAI_GCMUL 200 /* GC runs 'twice the speed' of memory allocation */ + + + +/* +@@ LUA_COMPAT_GETN controls compatibility with old getn behavior. +** CHANGE it (define it) if you want exact compatibility with the +** behavior of setn/getn in Lua 5.0. +*/ +#define LUA_COMPAT_GETN /* BS25 #undef LUA_COMPAT_GETN */ + +/* +@@ LUA_COMPAT_LOADLIB controls compatibility about global loadlib. +** CHANGE it to undefined as soon as you do not need a global 'loadlib' +** function (the function is still available as 'package.loadlib'). +*/ +#undef LUA_COMPAT_LOADLIB + +/* +@@ LUA_COMPAT_VARARG controls compatibility with old vararg feature. +** CHANGE it to undefined as soon as your programs use only '...' to +** access vararg parameters (instead of the old 'arg' table). +*/ +#define LUA_COMPAT_VARARG + +/* +@@ LUA_COMPAT_MOD controls compatibility with old math.mod function. +** CHANGE it to undefined as soon as your programs use 'math.fmod' or +** the new '%' operator instead of 'math.mod'. +*/ +#define LUA_COMPAT_MOD + +/* +@@ LUA_COMPAT_LSTR controls compatibility with old long string nesting +@* facility. +** CHANGE it to 2 if you want the old behaviour, or undefine it to turn +** off the advisory error when nesting [[...]]. +*/ +#define LUA_COMPAT_LSTR 1 + +/* +@@ LUA_COMPAT_GFIND controls compatibility with old 'string.gfind' name. +** CHANGE it to undefined as soon as you rename 'string.gfind' to +** 'string.gmatch'. +*/ +#define LUA_COMPAT_GFIND + +/* +@@ LUA_COMPAT_OPENLIB controls compatibility with old 'luaL_openlib' +@* behavior. +** CHANGE it to undefined as soon as you replace to 'luaL_register' +** your uses of 'luaL_openlib' +*/ +#define LUA_COMPAT_OPENLIB + + + +/* +@@ luai_apicheck is the assert macro used by the Lua-C API. +** CHANGE luai_apicheck if you want Lua to perform some checks in the +** parameters it gets from API calls. This may slow down the interpreter +** a bit, but may be quite useful when debugging C code that interfaces +** with Lua. A useful redefinition is to use assert.h. +*/ +#if defined(LUA_USE_APICHECK) +#include <assert.h> +#define luai_apicheck(L,o) { (void)L; assert(o); } +#else +#define luai_apicheck(L,o) { (void)L; } +#endif + + +/* +@@ LUAI_BITSINT defines the number of bits in an int. +** CHANGE here if Lua cannot automatically detect the number of bits of +** your machine. Probably you do not need to change this. +*/ +/* avoid overflows in comparison */ +#if INT_MAX-20 < 32760 +#define LUAI_BITSINT 16 +#elif INT_MAX > 2147483640L +/* int has at least 32 bits */ +#define LUAI_BITSINT 32 +#else +#error "you must define LUA_BITSINT with number of bits in an integer" +#endif + + +/* +@@ LUAI_UINT32 is an unsigned integer with at least 32 bits. +@@ LUAI_INT32 is an signed integer with at least 32 bits. +@@ LUAI_UMEM is an unsigned integer big enough to count the total +@* memory used by Lua. +@@ LUAI_MEM is a signed integer big enough to count the total memory +@* used by Lua. +** CHANGE here if for some weird reason the default definitions are not +** good enough for your machine. (The definitions in the 'else' +** part always works, but may waste space on machines with 64-bit +** longs.) Probably you do not need to change this. +*/ +#if LUAI_BITSINT >= 32 +#define LUAI_UINT32 unsigned int +#define LUAI_INT32 int +#define LUAI_MAXINT32 INT_MAX +#define LUAI_UMEM size_t +#define LUAI_MEM ptrdiff_t +#else +/* 16-bit ints */ +#define LUAI_UINT32 unsigned long +#define LUAI_INT32 long +#define LUAI_MAXINT32 LONG_MAX +#define LUAI_UMEM unsigned long +#define LUAI_MEM long +#endif + + +/* +@@ LUAI_MAXCALLS limits the number of nested calls. +** CHANGE it if you need really deep recursive calls. This limit is +** arbitrary; its only purpose is to stop infinite recursion before +** exhausting memory. +*/ +#define LUAI_MAXCALLS 20000 + + +/* +@@ LUAI_MAXCSTACK limits the number of Lua stack slots that a C function +@* can use. +** CHANGE it if you need lots of (Lua) stack space for your C +** functions. This limit is arbitrary; its only purpose is to stop C +** functions to consume unlimited stack space. +*/ +#define LUAI_MCS_AUX ((int)(INT_MAX / (4*sizeof(LUA_NUMBER)))) +#define LUAI_MAXCSTACK (LUAI_MCS_AUX > SHRT_MAX ? SHRT_MAX : LUAI_MCS_AUX) + + + +/* +** {================================================================== +** CHANGE (to smaller values) the following definitions if your system +** has a small C stack. (Or you may want to change them to larger +** values if your system has a large C stack and these limits are +** too rigid for you.) Some of these constants control the size of +** stack-allocated arrays used by the compiler or the interpreter, while +** others limit the maximum number of recursive calls that the compiler +** or the interpreter can perform. Values too large may cause a C stack +** overflow for some forms of deep constructs. +** =================================================================== +*/ + + +/* +@@ LUAI_MAXCCALLS is the maximum depth for nested C calls (short) and +@* syntactical nested non-terminals in a program. +*/ +#define LUAI_MAXCCALLS 200 + + +/* +@@ LUAI_MAXVARS is the maximum number of local variables per function +@* (must be smaller than 250). +*/ +#define LUAI_MAXVARS 200 + + +/* +@@ LUAI_MAXUPVALUES is the maximum number of upvalues per function +@* (must be smaller than 250). +*/ +#define LUAI_MAXUPVALUES 60 + + +/* +@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. +*/ +#define LUAL_BUFFERSIZE BUFSIZ + +/* }================================================================== */ + + + + +/* +** {================================================================== +@@ LUA_NUMBER is the type of numbers in Lua. +** CHANGE the following definitions only if you want to build Lua +** with a number type different from double. You may also need to +** change lua_number2int & lua_number2integer. +** =================================================================== +*/ + +#define LUA_NUMBER_DOUBLE +#define LUA_NUMBER double + +/* +@@ LUAI_UACNUMBER is the result of an 'usual argument conversion' +@* over a number. +*/ +#define LUAI_UACNUMBER double + + +/* +@@ LUA_NUMBER_SCAN is the format for reading numbers. +@@ LUA_NUMBER_FMT is the format for writing numbers. +@@ lua_number2str converts a number to a string. +@@ LUAI_MAXNUMBER2STR is maximum size of previous conversion. +@@ lua_str2number converts a string to a number. +*/ +#define LUA_NUMBER_SCAN "%lf" +#define LUA_NUMBER_FMT "%.14g" +#define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n)) +#define LUAI_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */ +#define lua_str2number(s,p) strtod((s), (p)) + + +/* +@@ The luai_num* macros define the primitive operations over numbers. +*/ +#if defined(LUA_CORE) +#include <math.h> +#define luai_numadd(a,b) ((a)+(b)) +#define luai_numsub(a,b) ((a)-(b)) +#define luai_nummul(a,b) ((a)*(b)) +#define luai_numdiv(a,b) ((a)/(b)) +#define luai_nummod(a,b) ((a) - floor((a)/(b))*(b)) +#define luai_numpow(a,b) (pow(a,b)) +#define luai_numunm(a) (-(a)) +#define luai_numeq(a,b) ((a)==(b)) +#define luai_numlt(a,b) ((a)<(b)) +#define luai_numle(a,b) ((a)<=(b)) +#define luai_numisnan(a) (!luai_numeq((a), (a))) +#endif + + +/* +@@ lua_number2int is a macro to convert lua_Number to int. +@@ lua_number2integer is a macro to convert lua_Number to lua_Integer. +** CHANGE them if you know a faster way to convert a lua_Number to +** int (with any rounding method and without throwing errors) in your +** system. In Pentium machines, a naive typecast from double to int +** in C is extremely slow, so any alternative is worth trying. +*/ + +/* On a Pentium, resort to a trick */ +#if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) && !defined(__SSE2__) && \ + (defined(__i386) || defined (_M_IX86) || defined(__i386__)) + +/* On a Microsoft compiler, use assembler */ +#if defined(_MSC_VER) + +#define lua_number2int(i,d) __asm fld d __asm fistp i +#define lua_number2integer(i,n) lua_number2int(i, n) + +/* the next trick should work on any Pentium, but sometimes clashes + with a DirectX idiosyncrasy */ +#else + +union luai_Cast { double l_d; long l_l; }; +#define lua_number2int(i,d) \ + { volatile union luai_Cast u; u.l_d = (d) + 6755399441055744.0; (i) = u.l_l; } +#define lua_number2integer(i,n) lua_number2int(i, n) + +#endif + + +/* this option always works, but may be slow */ +#else +#define lua_number2int(i,d) ((i)=(int)(d)) +#define lua_number2integer(i,d) ((i)=(lua_Integer)(d)) + +#endif + +/* }================================================================== */ + + +/* +@@ LUAI_USER_ALIGNMENT_T is a type that requires maximum alignment. +** CHANGE it if your system requires alignments larger than double. (For +** instance, if your system supports long doubles and they must be +** aligned in 16-byte boundaries, then you should add long double in the +** union.) Probably you do not need to change this. +*/ +#define LUAI_USER_ALIGNMENT_T union { double u; void *s; long l; } + + +/* +@@ LUAI_THROW/LUAI_TRY define how Lua does exception handling. +** CHANGE them if you prefer to use longjmp/setjmp even with C++ +** or if want/don't to use _longjmp/_setjmp instead of regular +** longjmp/setjmp. By default, Lua handles errors with exceptions when +** compiling as C++ code, with _longjmp/_setjmp when asked to use them, +** and with longjmp/setjmp otherwise. +*/ +#if 0 /* defined(__cplusplus) */ +/* C++ exceptions */ +#define LUAI_THROW(L,c) throw(c) +#define LUAI_TRY(L,c,a) try { a } catch(...) \ + { if ((c)->status == 0) (c)->status = -1; } +#define luai_jmpbuf int /* dummy variable */ + +#elif defined(LUA_USE_ULONGJMP) +/* in Unix, try _longjmp/_setjmp (more efficient) */ +#define LUAI_THROW(L,c) _longjmp((c)->b, 1) +#define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a } +#define luai_jmpbuf jmp_buf + +#else +/* default handling with long jumps */ +#define LUAI_THROW(L,c) longjmp((c)->b, 1) +#define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } +#define luai_jmpbuf jmp_buf + +#endif + + +/* +@@ LUA_MAXCAPTURES is the maximum number of captures that a pattern +@* can do during pattern-matching. +** CHANGE it if you need more captures. This limit is arbitrary. +*/ +#define LUA_MAXCAPTURES 32 + + +/* +@@ lua_tmpnam is the function that the OS library uses to create a +@* temporary name. +@@ LUA_TMPNAMBUFSIZE is the maximum size of a name created by lua_tmpnam. +** CHANGE them if you have an alternative to tmpnam (which is considered +** insecure) or if you want the original tmpnam anyway. By default, Lua +** uses tmpnam except when POSIX is available, where it uses mkstemp. +*/ +#if defined(loslib_c) || defined(luaall_c) + +#if defined(LUA_USE_MKSTEMP) +#include <unistd.h> +#define LUA_TMPNAMBUFSIZE 32 +#define lua_tmpnam(b,e) { \ + strcpy(b, "/tmp/lua_XXXXXX"); \ + e = mkstemp(b); \ + if (e != -1) close(e); \ + e = (e == -1); } + +#else +#define LUA_TMPNAMBUFSIZE L_tmpnam +#define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } +#endif + +#endif + + +/* +@@ lua_popen spawns a new process connected to the current one through +@* the file streams. +** CHANGE it if you have a way to implement it in your system. +*/ +#if defined(LUA_USE_POPEN) + +#define lua_popen(L,c,m) ((void)L, fflush(NULL), popen(c,m)) +#define lua_pclose(L,file) ((void)L, (pclose(file) != -1)) + +#elif defined(LUA_WIN) + +#define lua_popen(L,c,m) ((void)L, _popen(c,m)) +#define lua_pclose(L,file) ((void)L, (_pclose(file) != -1)) + +#else + +#define lua_popen(L,c,m) ((void)((void)c, m), \ + luaL_error(L, LUA_QL("popen") " not supported"), (FILE*)0) +#define lua_pclose(L,file) ((void)((void)L, file), 0) + +#endif + +/* +@@ LUA_DL_* define which dynamic-library system Lua should use. +** CHANGE here if Lua has problems choosing the appropriate +** dynamic-library system for your platform (either Windows' DLL, Mac's +** dyld, or Unix's dlopen). If your system is some kind of Unix, there +** is a good chance that it has dlopen, so LUA_DL_DLOPEN will work for +** it. To use dlopen you also need to adapt the src/Makefile (probably +** adding -ldl to the linker options), so Lua does not select it +** automatically. (When you change the makefile to add -ldl, you must +** also add -DLUA_USE_DLOPEN.) +** If you do not want any kind of dynamic library, undefine all these +** options. +** By default, _WIN32 gets LUA_DL_DLL and MAC OS X gets LUA_DL_DYLD. +*/ +#if defined(LUA_USE_DLOPEN) +#define LUA_DL_DLOPEN +#endif + +#if defined(LUA_WIN) +#define LUA_DL_DLL +#endif + + +/* +@@ LUAI_EXTRASPACE allows you to add user-specific data in a lua_State +@* (the data goes just *before* the lua_State pointer). +** CHANGE (define) this if you really need that. This value must be +** a multiple of the maximum alignment required for your machine. +*/ +#define LUAI_EXTRASPACE 0 + + +/* +@@ luai_userstate* allow user-specific actions on threads. +** CHANGE them if you defined LUAI_EXTRASPACE and need to do something +** extra when a thread is created/deleted/resumed/yielded. +*/ +#define luai_userstateopen(L) ((void)L) +#define luai_userstateclose(L) ((void)L) +#define luai_userstatethread(L,L1) ((void)L) +#define luai_userstatefree(L) ((void)L) +#define luai_userstateresume(L,n) ((void)L) +#define luai_userstateyield(L,n) ((void)L) + + +/* +@@ LUA_INTFRMLEN is the length modifier for integer conversions +@* in 'string.format'. +@@ LUA_INTFRM_T is the integer type correspoding to the previous length +@* modifier. +** CHANGE them if your system supports long long or does not support long. +*/ + +#if defined(LUA_USELONGLONG) + +#define LUA_INTFRMLEN "ll" +#define LUA_INTFRM_T long long + +#else + +#define LUA_INTFRMLEN "l" +#define LUA_INTFRM_T long + +#endif + + + +/* =================================================================== */ + +/* +** Local configuration. You can use this space to add your redefinitions +** without modifying the main part of the file. +*/ + + + +#endif + diff --git a/engines/sword25/util/lua/lualib.h b/engines/sword25/util/lua/lualib.h new file mode 100644 index 0000000000..33d4e314c2 --- /dev/null +++ b/engines/sword25/util/lua/lualib.h @@ -0,0 +1,53 @@ +/* +** $Id$ +** Lua standard libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lualib_h +#define lualib_h + +#include "lua.h" + + +/* Key to file-handle type */ +#define LUA_FILEHANDLE "FILE*" + + +#define LUA_COLIBNAME "coroutine" +LUALIB_API int (luaopen_base) (lua_State *L); + +#define LUA_TABLIBNAME "table" +LUALIB_API int (luaopen_table) (lua_State *L); + +#define LUA_IOLIBNAME "io" +LUALIB_API int (luaopen_io) (lua_State *L); + +#define LUA_OSLIBNAME "os" +LUALIB_API int (luaopen_os) (lua_State *L); + +#define LUA_STRLIBNAME "string" +LUALIB_API int (luaopen_string) (lua_State *L); + +#define LUA_MATHLIBNAME "math" +LUALIB_API int (luaopen_math) (lua_State *L); + +#define LUA_DBLIBNAME "debug" +LUALIB_API int (luaopen_debug) (lua_State *L); + +#define LUA_LOADLIBNAME "package" +LUALIB_API int (luaopen_package) (lua_State *L); + + +/* open all previous libraries */ +LUALIB_API void (luaL_openlibs) (lua_State *L); + + + +#ifndef lua_assert +#define lua_assert(x) ((void)0) +#endif + + +#endif diff --git a/engines/sword25/util/lua/lundump.cpp b/engines/sword25/util/lua/lundump.cpp new file mode 100644 index 0000000000..4ffc623575 --- /dev/null +++ b/engines/sword25/util/lua/lundump.cpp @@ -0,0 +1,225 @@ +/* +** $Id$ +** load precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#include <string.h> + +#define lundump_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lmem.h" +#include "lobject.h" +#include "lstring.h" +#include "lundump.h" +#include "lzio.h" + +typedef struct { + lua_State* L; + ZIO* Z; + Mbuffer* b; + const char* name; +} LoadState; + +#ifdef LUAC_TRUST_BINARIES +#define IF(c,s) +#define error(S,s) +#else +#define IF(c,s) if (c) error(S,s) + +static void error(LoadState* S, const char* why) +{ + luaO_pushfstring(S->L,"%s: %s in precompiled chunk",S->name,why); + luaD_throw(S->L,LUA_ERRSYNTAX); +} +#endif + +#define LoadMem(S,b,n,size) LoadBlock(S,b,(n)*(size)) +#define LoadByte(S) (lu_byte)LoadChar(S) +#define LoadVar(S,x) LoadMem(S,&x,1,sizeof(x)) +#define LoadVector(S,b,n,size) LoadMem(S,b,n,size) + +static void LoadBlock(LoadState* S, void* b, size_t size) +{ + size_t r=luaZ_read(S->Z,b,size); + UNUSED(r); + IF (r!=0, "unexpected end"); +} + +static int LoadChar(LoadState* S) +{ + char x; + LoadVar(S,x); + return x; +} + +static int LoadInt(LoadState* S) +{ + int x; + LoadVar(S,x); + IF (x<0, "bad integer"); + return x; +} + +static lua_Number LoadNumber(LoadState* S) +{ + lua_Number x; + LoadVar(S,x); + return x; +} + +static TString* LoadString(LoadState* S) +{ + size_t size; + LoadVar(S,size); + if (size==0) + return NULL; + else + { + char* s=luaZ_openspace(S->L,S->b,size); + LoadBlock(S,s,size); + return luaS_newlstr(S->L,s,size-1); /* remove trailing '\0' */ + } +} + +static void LoadCode(LoadState* S, Proto* f) +{ + int n=LoadInt(S); + f->code=luaM_newvector(S->L,n,Instruction); + f->sizecode=n; + LoadVector(S,f->code,n,sizeof(Instruction)); +} + +static Proto* LoadFunction(LoadState* S, TString* p); + +static void LoadConstants(LoadState* S, Proto* f) +{ + int i,n; + n=LoadInt(S); + f->k=luaM_newvector(S->L,n,TValue); + f->sizek=n; + for (i=0; i<n; i++) setnilvalue(&f->k[i]); + for (i=0; i<n; i++) + { + TValue* o=&f->k[i]; + int t=LoadChar(S); + switch (t) + { + case LUA_TNIL: + setnilvalue(o); + break; + case LUA_TBOOLEAN: + setbvalue(o,LoadChar(S)); + break; + case LUA_TNUMBER: + setnvalue(o,LoadNumber(S)); + break; + case LUA_TSTRING: + setsvalue2n(S->L,o,LoadString(S)); + break; + default: + error(S,"bad constant"); + break; + } + } + n=LoadInt(S); + f->p=luaM_newvector(S->L,n,Proto*); + f->sizep=n; + for (i=0; i<n; i++) f->p[i]=NULL; + for (i=0; i<n; i++) f->p[i]=LoadFunction(S,f->source); +} + +static void LoadDebug(LoadState* S, Proto* f) +{ + int i,n; + n=LoadInt(S); + f->lineinfo=luaM_newvector(S->L,n,int); + f->sizelineinfo=n; + LoadVector(S,f->lineinfo,n,sizeof(int)); + n=LoadInt(S); + f->locvars=luaM_newvector(S->L,n,LocVar); + f->sizelocvars=n; + for (i=0; i<n; i++) f->locvars[i].varname=NULL; + for (i=0; i<n; i++) + { + f->locvars[i].varname=LoadString(S); + f->locvars[i].startpc=LoadInt(S); + f->locvars[i].endpc=LoadInt(S); + } + n=LoadInt(S); + f->upvalues=luaM_newvector(S->L,n,TString*); + f->sizeupvalues=n; + for (i=0; i<n; i++) f->upvalues[i]=NULL; + for (i=0; i<n; i++) f->upvalues[i]=LoadString(S); +} + +static Proto* LoadFunction(LoadState* S, TString* p) +{ + Proto* f=luaF_newproto(S->L); + setptvalue2s(S->L,S->L->top,f); incr_top(S->L); + f->source=LoadString(S); if (f->source==NULL) f->source=p; + f->linedefined=LoadInt(S); + f->lastlinedefined=LoadInt(S); + f->nups=LoadByte(S); + f->numparams=LoadByte(S); + f->is_vararg=LoadByte(S); + f->maxstacksize=LoadByte(S); + LoadCode(S,f); + LoadConstants(S,f); + LoadDebug(S,f); + IF (!luaG_checkcode(f), "bad code"); + S->L->top--; + return f; +} + +static void LoadHeader(LoadState* S) +{ + char h[LUAC_HEADERSIZE]; + char s[LUAC_HEADERSIZE]; + luaU_header(h); + LoadBlock(S,s,LUAC_HEADERSIZE); + IF (memcmp(h,s,LUAC_HEADERSIZE)!=0, "bad header"); +} + +/* +** load precompiled chunk +*/ +Proto* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name) +{ + LoadState S; + if (*name=='@' || *name=='=') + S.name=name+1; + else if (*name==LUA_SIGNATURE[0]) + S.name="binary string"; + else + S.name=name; + S.L=L; + S.Z=Z; + S.b=buff; + LoadHeader(&S); + return LoadFunction(&S,luaS_newliteral(L,"=?")); +} + +/* +* make header +*/ +void luaU_header (char* h) +{ + int x=1; + memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-1); + h+=sizeof(LUA_SIGNATURE)-1; + *h++=(char)LUAC_VERSION; + *h++=(char)LUAC_FORMAT; + *h++=(char)*(char*)&x; /* endianness */ + *h++=(char)sizeof(int); + *h++=(char)sizeof(size_t); + *h++=(char)sizeof(Instruction); + *h++=(char)sizeof(lua_Number); + *h++=(char)(((lua_Number)0.5)==0); /* is lua_Number integral? */ +} diff --git a/engines/sword25/util/lua/lundump.h b/engines/sword25/util/lua/lundump.h new file mode 100644 index 0000000000..f791a4f173 --- /dev/null +++ b/engines/sword25/util/lua/lundump.h @@ -0,0 +1,36 @@ +/* +** $Id$ +** load precompiled Lua chunks +** See Copyright Notice in lua.h +*/ + +#ifndef lundump_h +#define lundump_h + +#include "lobject.h" +#include "lzio.h" + +/* load one chunk; from lundump.c */ +LUAI_FUNC Proto* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name); + +/* make header; from lundump.c */ +LUAI_FUNC void luaU_header (char* h); + +/* dump one chunk; from ldump.c */ +LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip); + +#ifdef luac_c +/* print one chunk; from print.c */ +LUAI_FUNC void luaU_print (const Proto* f, int full); +#endif + +/* for header of binary files -- this is Lua 5.1 */ +#define LUAC_VERSION 0x51 + +/* for header of binary files -- this is the official format */ +#define LUAC_FORMAT 0 + +/* size of header of binary files */ +#define LUAC_HEADERSIZE 12 + +#endif diff --git a/engines/sword25/util/lua/lvm.cpp b/engines/sword25/util/lua/lvm.cpp new file mode 100644 index 0000000000..ae70fe2645 --- /dev/null +++ b/engines/sword25/util/lua/lvm.cpp @@ -0,0 +1,763 @@ +/* +** $Id$ +** Lua virtual machine +** See Copyright Notice in lua.h +*/ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define lvm_c +#define LUA_CORE + +#include "lua.h" + +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lvm.h" + + + +/* limit for table tag-method chains (to avoid loops) */ +#define MAXTAGLOOP 100 + + +const TValue *luaV_tonumber (const TValue *obj, TValue *n) { + lua_Number num; + if (ttisnumber(obj)) return obj; + if (ttisstring(obj) && luaO_str2d(svalue(obj), &num)) { + setnvalue(n, num); + return n; + } + else + return NULL; +} + + +int luaV_tostring (lua_State *L, StkId obj) { + if (!ttisnumber(obj)) + return 0; + else { + char s[LUAI_MAXNUMBER2STR]; + lua_Number n = nvalue(obj); + lua_number2str(s, n); + setsvalue2s(L, obj, luaS_new(L, s)); + return 1; + } +} + + +static void traceexec (lua_State *L, const Instruction *pc) { + lu_byte mask = L->hookmask; + const Instruction *oldpc = L->savedpc; + L->savedpc = pc; + if ((mask & LUA_MASKCOUNT) && L->hookcount == 0) { + resethookcount(L); + luaD_callhook(L, LUA_HOOKCOUNT, -1); + } + if (mask & LUA_MASKLINE) { + Proto *p = ci_func(L->ci)->l.p; + int npc = pcRel(pc, p); + int newline = getline(p, npc); + /* call linehook when enter a new function, when jump back (loop), + or when enter a new line */ + if (npc == 0 || pc <= oldpc || newline != getline(p, pcRel(oldpc, p))) + luaD_callhook(L, LUA_HOOKLINE, newline); + } +} + + +static void callTMres (lua_State *L, StkId res, const TValue *f, + const TValue *p1, const TValue *p2) { + ptrdiff_t result = savestack(L, res); + setobj2s(L, L->top, f); /* push function */ + setobj2s(L, L->top+1, p1); /* 1st argument */ + setobj2s(L, L->top+2, p2); /* 2nd argument */ + luaD_checkstack(L, 3); + L->top += 3; + luaD_call(L, L->top - 3, 1); + res = restorestack(L, result); + L->top--; + setobjs2s(L, res, L->top); +} + + + +static void callTM (lua_State *L, const TValue *f, const TValue *p1, + const TValue *p2, const TValue *p3) { + setobj2s(L, L->top, f); /* push function */ + setobj2s(L, L->top+1, p1); /* 1st argument */ + setobj2s(L, L->top+2, p2); /* 2nd argument */ + setobj2s(L, L->top+3, p3); /* 3th argument */ + luaD_checkstack(L, 4); + L->top += 4; + luaD_call(L, L->top - 4, 0); +} + + +void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) { + int loop; + for (loop = 0; loop < MAXTAGLOOP; loop++) { + const TValue *tm; + if (ttistable(t)) { /* `t' is a table? */ + Table *h = hvalue(t); + const TValue *res = luaH_get(h, key); /* do a primitive get */ + if (!ttisnil(res) || /* result is no nil? */ + (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */ + setobj2s(L, val, res); + return; + } + /* else will try the tag method */ + } + else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) + luaG_typeerror(L, t, "index"); + if (ttisfunction(tm)) { + callTMres(L, val, tm, t, key); + return; + } + t = tm; /* else repeat with `tm' */ + } + luaG_runerror(L, "loop in gettable"); +} + + +void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { + int loop; + for (loop = 0; loop < MAXTAGLOOP; loop++) { + const TValue *tm; + if (ttistable(t)) { /* `t' is a table? */ + Table *h = hvalue(t); + TValue *oldval = luaH_set(L, h, key); /* do a primitive set */ + if (!ttisnil(oldval) || /* result is no nil? */ + (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */ + setobj2t(L, oldval, val); + luaC_barriert(L, h, val); + return; + } + /* else will try the tag method */ + } + else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) + luaG_typeerror(L, t, "index"); + if (ttisfunction(tm)) { + callTM(L, tm, t, key, val); + return; + } + t = tm; /* else repeat with `tm' */ + } + luaG_runerror(L, "loop in settable"); +} + + +static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2, + StkId res, TMS event) { + const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ + if (ttisnil(tm)) + tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ + if (ttisnil(tm)) return 0; + callTMres(L, res, tm, p1, p2); + return 1; +} + + +static const TValue *get_compTM (lua_State *L, Table *mt1, Table *mt2, + TMS event) { + const TValue *tm1 = fasttm(L, mt1, event); + const TValue *tm2; + if (tm1 == NULL) return NULL; /* no metamethod */ + if (mt1 == mt2) return tm1; /* same metatables => same metamethods */ + tm2 = fasttm(L, mt2, event); + if (tm2 == NULL) return NULL; /* no metamethod */ + if (luaO_rawequalObj(tm1, tm2)) /* same metamethods? */ + return tm1; + return NULL; +} + + +static int call_orderTM (lua_State *L, const TValue *p1, const TValue *p2, + TMS event) { + const TValue *tm1 = luaT_gettmbyobj(L, p1, event); + const TValue *tm2; + if (ttisnil(tm1)) return -1; /* no metamethod? */ + tm2 = luaT_gettmbyobj(L, p2, event); + if (!luaO_rawequalObj(tm1, tm2)) /* different metamethods? */ + return -1; + callTMres(L, L->top, tm1, p1, p2); + return !l_isfalse(L->top); +} + + +static int l_strcmp (const TString *ls, const TString *rs) { + const char *l = getstr(ls); + size_t ll = ls->tsv.len; + const char *r = getstr(rs); + size_t lr = rs->tsv.len; + for (;;) { + int temp = strcoll(l, r); + if (temp != 0) return temp; + else { /* strings are equal up to a `\0' */ + size_t len = strlen(l); /* index of first `\0' in both strings */ + if (len == lr) /* r is finished? */ + return (len == ll) ? 0 : 1; + else if (len == ll) /* l is finished? */ + return -1; /* l is smaller than r (because r is not finished) */ + /* both strings longer than `len'; go on comparing (after the `\0') */ + len++; + l += len; ll -= len; r += len; lr -= len; + } + } +} + + +int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { + int res; + if (ttype(l) != ttype(r)) + return luaG_ordererror(L, l, r); + else if (ttisnumber(l)) + return luai_numlt(nvalue(l), nvalue(r)); + else if (ttisstring(l)) + return l_strcmp(rawtsvalue(l), rawtsvalue(r)) < 0; + else if ((res = call_orderTM(L, l, r, TM_LT)) != -1) + return res; + return luaG_ordererror(L, l, r); +} + + +static int lessequal (lua_State *L, const TValue *l, const TValue *r) { + int res; + if (ttype(l) != ttype(r)) + return luaG_ordererror(L, l, r); + else if (ttisnumber(l)) + return luai_numle(nvalue(l), nvalue(r)); + else if (ttisstring(l)) + return l_strcmp(rawtsvalue(l), rawtsvalue(r)) <= 0; + else if ((res = call_orderTM(L, l, r, TM_LE)) != -1) /* first try `le' */ + return res; + else if ((res = call_orderTM(L, r, l, TM_LT)) != -1) /* else try `lt' */ + return !res; + return luaG_ordererror(L, l, r); +} + + +int luaV_equalval (lua_State *L, const TValue *t1, const TValue *t2) { + const TValue *tm; + lua_assert(ttype(t1) == ttype(t2)); + switch (ttype(t1)) { + case LUA_TNIL: return 1; + case LUA_TNUMBER: return luai_numeq(nvalue(t1), nvalue(t2)); + case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ + case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); + case LUA_TUSERDATA: { + if (uvalue(t1) == uvalue(t2)) return 1; + tm = get_compTM(L, uvalue(t1)->metatable, uvalue(t2)->metatable, + TM_EQ); + break; /* will try TM */ + } + case LUA_TTABLE: { + if (hvalue(t1) == hvalue(t2)) return 1; + tm = get_compTM(L, hvalue(t1)->metatable, hvalue(t2)->metatable, TM_EQ); + break; /* will try TM */ + } + default: return gcvalue(t1) == gcvalue(t2); + } + if (tm == NULL) return 0; /* no TM? */ + callTMres(L, L->top, tm, t1, t2); /* call TM */ + return !l_isfalse(L->top); +} + + +void luaV_concat (lua_State *L, int total, int last) { + do { + StkId top = L->base + last + 1; + int n = 2; /* number of elements handled in this pass (at least 2) */ + if (!(ttisstring(top-2) || ttisnumber(top-2)) || !tostring(L, top-1)) { + if (!call_binTM(L, top-2, top-1, top-2, TM_CONCAT)) + luaG_concaterror(L, top-2, top-1); + } else if (tsvalue(top-1)->len == 0) /* second op is empty? */ + (void)tostring(L, top - 2); /* result is first op (as string) */ + else { + /* at least two string values; get as many as possible */ + size_t tl = tsvalue(top-1)->len; + char *buffer; + int i; + /* collect total length */ + for (n = 1; n < total && tostring(L, top-n-1); n++) { + size_t l = tsvalue(top-n-1)->len; + if (l >= MAX_SIZET - tl) luaG_runerror(L, "string length overflow"); + tl += l; + } + buffer = luaZ_openspace(L, &G(L)->buff, tl); + tl = 0; + for (i=n; i>0; i--) { /* concat all strings */ + size_t l = tsvalue(top-i)->len; + memcpy(buffer+tl, svalue(top-i), l); + tl += l; + } + setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl)); + } + total -= n-1; /* got `n' strings to create 1 new */ + last -= n-1; + } while (total > 1); /* repeat until only 1 result left */ +} + + +static void Arith (lua_State *L, StkId ra, const TValue *rb, + const TValue *rc, TMS op) { + TValue tempb, tempc; + const TValue *b, *c; + if ((b = luaV_tonumber(rb, &tempb)) != NULL && + (c = luaV_tonumber(rc, &tempc)) != NULL) { + lua_Number nb = nvalue(b), nc = nvalue(c); + switch (op) { + case TM_ADD: setnvalue(ra, luai_numadd(nb, nc)); break; + case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break; + case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break; + case TM_DIV: setnvalue(ra, luai_numdiv(nb, nc)); break; + case TM_MOD: setnvalue(ra, luai_nummod(nb, nc)); break; + case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break; + case TM_UNM: setnvalue(ra, luai_numunm(nb)); break; + default: lua_assert(0); break; + } + } + else if (!call_binTM(L, rb, rc, ra, op)) + luaG_aritherror(L, rb, rc); +} + + + +/* +** some macros for common tasks in `luaV_execute' +*/ + +#define runtime_check(L, c) { if (!(c)) break; } + +#define RA(i) (base+GETARG_A(i)) +/* to be used after possible stack reallocation */ +#define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i)) +#define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i)) +#define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \ + ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i)) +#define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \ + ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i)) +#define KBx(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i)) + + +#define dojump(L,pc,i) {(pc) += (i); luai_threadyield(L);} + + +#define Protect(x) { L->savedpc = pc; {x;}; base = L->base; } + + +#define arith_op(op,tm) { \ + TValue *rb = RKB(i); \ + TValue *rc = RKC(i); \ + if (ttisnumber(rb) && ttisnumber(rc)) { \ + lua_Number nb = nvalue(rb), nc = nvalue(rc); \ + setnvalue(ra, op(nb, nc)); \ + } \ + else \ + Protect(Arith(L, ra, rb, rc, tm)); \ + } + + + +void luaV_execute (lua_State *L, int nexeccalls) { + LClosure *cl; + StkId base; + TValue *k; + const Instruction *pc; + reentry: /* entry point */ + lua_assert(isLua(L->ci)); + pc = L->savedpc; + cl = &clvalue(L->ci->func)->l; + base = L->base; + k = cl->p->k; + /* main loop of interpreter */ + for (;;) { + const Instruction i = *pc++; + StkId ra; + if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && + (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) { + traceexec(L, pc); + if (L->status == LUA_YIELD) { /* did hook yield? */ + L->savedpc = pc - 1; + return; + } + base = L->base; + } + /* warning!! several calls may realloc the stack and invalidate `ra' */ + ra = RA(i); + lua_assert(base == L->base && L->base == L->ci->base); + lua_assert(base <= L->top && L->top <= L->stack + L->stacksize); + lua_assert(L->top == L->ci->top || luaG_checkopenop(i)); + switch (GET_OPCODE(i)) { + case OP_MOVE: { + setobjs2s(L, ra, RB(i)); + continue; + } + case OP_LOADK: { + setobj2s(L, ra, KBx(i)); + continue; + } + case OP_LOADBOOL: { + setbvalue(ra, GETARG_B(i)); + if (GETARG_C(i)) pc++; /* skip next instruction (if C) */ + continue; + } + case OP_LOADNIL: { + TValue *rb = RB(i); + do { + setnilvalue(rb--); + } while (rb >= ra); + continue; + } + case OP_GETUPVAL: { + int b = GETARG_B(i); + setobj2s(L, ra, cl->upvals[b]->v); + continue; + } + case OP_GETGLOBAL: { + TValue g; + TValue *rb = KBx(i); + sethvalue(L, &g, cl->env); + lua_assert(ttisstring(rb)); + Protect(luaV_gettable(L, &g, rb, ra)); + continue; + } + case OP_GETTABLE: { + Protect(luaV_gettable(L, RB(i), RKC(i), ra)); + continue; + } + case OP_SETGLOBAL: { + TValue g; + sethvalue(L, &g, cl->env); + lua_assert(ttisstring(KBx(i))); + Protect(luaV_settable(L, &g, KBx(i), ra)); + continue; + } + case OP_SETUPVAL: { + UpVal *uv = cl->upvals[GETARG_B(i)]; + setobj(L, uv->v, ra); + luaC_barrier(L, uv, ra); + continue; + } + case OP_SETTABLE: { + Protect(luaV_settable(L, ra, RKB(i), RKC(i))); + continue; + } + case OP_NEWTABLE: { + int b = GETARG_B(i); + int c = GETARG_C(i); + sethvalue(L, ra, luaH_new(L, luaO_fb2int(b), luaO_fb2int(c))); + Protect(luaC_checkGC(L)); + continue; + } + case OP_SELF: { + StkId rb = RB(i); + setobjs2s(L, ra+1, rb); + Protect(luaV_gettable(L, rb, RKC(i), ra)); + continue; + } + case OP_ADD: { + arith_op(luai_numadd, TM_ADD); + continue; + } + case OP_SUB: { + arith_op(luai_numsub, TM_SUB); + continue; + } + case OP_MUL: { + arith_op(luai_nummul, TM_MUL); + continue; + } + case OP_DIV: { + arith_op(luai_numdiv, TM_DIV); + continue; + } + case OP_MOD: { + arith_op(luai_nummod, TM_MOD); + continue; + } + case OP_POW: { + arith_op(luai_numpow, TM_POW); + continue; + } + case OP_UNM: { + TValue *rb = RB(i); + if (ttisnumber(rb)) { + lua_Number nb = nvalue(rb); + setnvalue(ra, luai_numunm(nb)); + } + else { + Protect(Arith(L, ra, rb, rb, TM_UNM)); + } + continue; + } + case OP_NOT: { + int res = l_isfalse(RB(i)); /* next assignment may change this value */ + setbvalue(ra, res); + continue; + } + case OP_LEN: { + const TValue *rb = RB(i); + switch (ttype(rb)) { + case LUA_TTABLE: { + setnvalue(ra, cast_num(luaH_getn(hvalue(rb)))); + break; + } + case LUA_TSTRING: { + setnvalue(ra, cast_num(tsvalue(rb)->len)); + break; + } + default: { /* try metamethod */ + Protect( + if (!call_binTM(L, rb, luaO_nilobject, ra, TM_LEN)) + luaG_typeerror(L, rb, "get length of"); + ) + } + } + continue; + } + case OP_CONCAT: { + int b = GETARG_B(i); + int c = GETARG_C(i); + Protect(luaV_concat(L, c-b+1, c); luaC_checkGC(L)); + setobjs2s(L, RA(i), base+b); + continue; + } + case OP_JMP: { + dojump(L, pc, GETARG_sBx(i)); + continue; + } + case OP_EQ: { + TValue *rb = RKB(i); + TValue *rc = RKC(i); + Protect( + if (equalobj(L, rb, rc) == GETARG_A(i)) + dojump(L, pc, GETARG_sBx(*pc)); + ) + pc++; + continue; + } + case OP_LT: { + Protect( + if (luaV_lessthan(L, RKB(i), RKC(i)) == GETARG_A(i)) + dojump(L, pc, GETARG_sBx(*pc)); + ) + pc++; + continue; + } + case OP_LE: { + Protect( + if (lessequal(L, RKB(i), RKC(i)) == GETARG_A(i)) + dojump(L, pc, GETARG_sBx(*pc)); + ) + pc++; + continue; + } + case OP_TEST: { + if (l_isfalse(ra) != GETARG_C(i)) + dojump(L, pc, GETARG_sBx(*pc)); + pc++; + continue; + } + case OP_TESTSET: { + TValue *rb = RB(i); + if (l_isfalse(rb) != GETARG_C(i)) { + setobjs2s(L, ra, rb); + dojump(L, pc, GETARG_sBx(*pc)); + } + pc++; + continue; + } + case OP_CALL: { + int b = GETARG_B(i); + int nresults = GETARG_C(i) - 1; + if (b != 0) L->top = ra+b; /* else previous instruction set top */ + L->savedpc = pc; + switch (luaD_precall(L, ra, nresults)) { + case PCRLUA: { + nexeccalls++; + goto reentry; /* restart luaV_execute over new Lua function */ + } + case PCRC: { + /* it was a C function (`precall' called it); adjust results */ + if (nresults >= 0) L->top = L->ci->top; + base = L->base; + continue; + } + default: { + return; /* yield */ + } + } + } + case OP_TAILCALL: { + int b = GETARG_B(i); + if (b != 0) L->top = ra+b; /* else previous instruction set top */ + L->savedpc = pc; + lua_assert(GETARG_C(i) - 1 == LUA_MULTRET); + switch (luaD_precall(L, ra, LUA_MULTRET)) { + case PCRLUA: { + /* tail call: put new frame in place of previous one */ + CallInfo *ci = L->ci - 1; /* previous frame */ + int aux; + StkId func = ci->func; + StkId pfunc = (ci+1)->func; /* previous function index */ + if (L->openupval) luaF_close(L, ci->base); + L->base = ci->base = ci->func + ((ci+1)->base - pfunc); + for (aux = 0; pfunc+aux < L->top; aux++) /* move frame down */ + setobjs2s(L, func+aux, pfunc+aux); + ci->top = L->top = func+aux; /* correct top */ + lua_assert(L->top == L->base + clvalue(func)->l.p->maxstacksize); + ci->savedpc = L->savedpc; + ci->tailcalls++; /* one more call lost */ + L->ci--; /* remove new frame */ + goto reentry; + } + case PCRC: { /* it was a C function (`precall' called it) */ + base = L->base; + continue; + } + default: { + return; /* yield */ + } + } + } + case OP_RETURN: { + int b = GETARG_B(i); + if (b != 0) L->top = ra+b-1; + if (L->openupval) luaF_close(L, base); + L->savedpc = pc; + b = luaD_poscall(L, ra); + if (--nexeccalls == 0) /* was previous function running `here'? */ + return; /* no: return */ + else { /* yes: continue its execution */ + if (b) L->top = L->ci->top; + lua_assert(isLua(L->ci)); + lua_assert(GET_OPCODE(*((L->ci)->savedpc - 1)) == OP_CALL); + goto reentry; + } + } + case OP_FORLOOP: { + lua_Number step = nvalue(ra+2); + lua_Number idx = luai_numadd(nvalue(ra), step); /* increment index */ + lua_Number limit = nvalue(ra+1); + if (luai_numlt(0, step) ? luai_numle(idx, limit) + : luai_numle(limit, idx)) { + dojump(L, pc, GETARG_sBx(i)); /* jump back */ + setnvalue(ra, idx); /* update internal index... */ + setnvalue(ra+3, idx); /* ...and external index */ + } + continue; + } + case OP_FORPREP: { + const TValue *init = ra; + const TValue *plimit = ra+1; + const TValue *pstep = ra+2; + L->savedpc = pc; /* next steps may throw errors */ + if (!tonumber(init, ra)) + luaG_runerror(L, LUA_QL("for") " initial value must be a number"); + else if (!tonumber(plimit, ra+1)) + luaG_runerror(L, LUA_QL("for") " limit must be a number"); + else if (!tonumber(pstep, ra+2)) + luaG_runerror(L, LUA_QL("for") " step must be a number"); + setnvalue(ra, luai_numsub(nvalue(ra), nvalue(pstep))); + dojump(L, pc, GETARG_sBx(i)); + continue; + } + case OP_TFORLOOP: { + StkId cb = ra + 3; /* call base */ + setobjs2s(L, cb+2, ra+2); + setobjs2s(L, cb+1, ra+1); + setobjs2s(L, cb, ra); + L->top = cb+3; /* func. + 2 args (state and index) */ + Protect(luaD_call(L, cb, GETARG_C(i))); + L->top = L->ci->top; + cb = RA(i) + 3; /* previous call may change the stack */ + if (!ttisnil(cb)) { /* continue loop? */ + setobjs2s(L, cb-1, cb); /* save control variable */ + dojump(L, pc, GETARG_sBx(*pc)); /* jump back */ + } + pc++; + continue; + } + case OP_SETLIST: { + int n = GETARG_B(i); + int c = GETARG_C(i); + int last; + Table *h; + if (n == 0) { + n = cast_int(L->top - ra) - 1; + L->top = L->ci->top; + } + if (c == 0) c = cast_int(*pc++); + runtime_check(L, ttistable(ra)); + h = hvalue(ra); + last = ((c-1)*LFIELDS_PER_FLUSH) + n; + if (last > h->sizearray) /* needs more space? */ + luaH_resizearray(L, h, last); /* pre-alloc it at once */ + for (; n > 0; n--) { + TValue *val = ra+n; + setobj2t(L, luaH_setnum(L, h, last--), val); + luaC_barriert(L, h, val); + } + continue; + } + case OP_CLOSE: { + luaF_close(L, ra); + continue; + } + case OP_CLOSURE: { + Proto *p; + Closure *ncl; + int nup, j; + p = cl->p->p[GETARG_Bx(i)]; + nup = p->nups; + ncl = luaF_newLclosure(L, nup, cl->env); + ncl->l.p = p; + for (j=0; j<nup; j++, pc++) { + if (GET_OPCODE(*pc) == OP_GETUPVAL) + ncl->l.upvals[j] = cl->upvals[GETARG_B(*pc)]; + else { + lua_assert(GET_OPCODE(*pc) == OP_MOVE); + ncl->l.upvals[j] = luaF_findupval(L, base + GETARG_B(*pc)); + } + } + setclvalue(L, ra, ncl); + Protect(luaC_checkGC(L)); + continue; + } + case OP_VARARG: { + int b = GETARG_B(i) - 1; + int j; + CallInfo *ci = L->ci; + int n = cast_int(ci->base - ci->func) - cl->p->numparams - 1; + if (b == LUA_MULTRET) { + Protect(luaD_checkstack(L, n)); + ra = RA(i); /* previous call may change the stack */ + b = n; + L->top = ra + n; + } + for (j = 0; j < b; j++) { + if (j < n) { + setobjs2s(L, ra + j, ci->base - n + j); + } + else { + setnilvalue(ra + j); + } + } + continue; + } + } + } +} + diff --git a/engines/sword25/util/lua/lvm.h b/engines/sword25/util/lua/lvm.h new file mode 100644 index 0000000000..dff2a139f7 --- /dev/null +++ b/engines/sword25/util/lua/lvm.h @@ -0,0 +1,36 @@ +/* +** $Id$ +** Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#ifndef lvm_h +#define lvm_h + + +#include "ldo.h" +#include "lobject.h" +#include "ltm.h" + + +#define tostring(L,o) ((ttype(o) == LUA_TSTRING) || (luaV_tostring(L, o))) + +#define tonumber(o,n) (ttype(o) == LUA_TNUMBER || \ + (((o) = luaV_tonumber(o,n)) != NULL)) + +#define equalobj(L,o1,o2) \ + (ttype(o1) == ttype(o2) && luaV_equalval(L, o1, o2)) + + +LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); +LUAI_FUNC int luaV_equalval (lua_State *L, const TValue *t1, const TValue *t2); +LUAI_FUNC const TValue *luaV_tonumber (const TValue *obj, TValue *n); +LUAI_FUNC int luaV_tostring (lua_State *L, StkId obj); +LUAI_FUNC void luaV_gettable (lua_State *L, const TValue *t, TValue *key, + StkId val); +LUAI_FUNC void luaV_settable (lua_State *L, const TValue *t, TValue *key, + StkId val); +LUAI_FUNC void luaV_execute (lua_State *L, int nexeccalls); +LUAI_FUNC void luaV_concat (lua_State *L, int total, int last); + +#endif diff --git a/engines/sword25/util/lua/lzio.cpp b/engines/sword25/util/lua/lzio.cpp new file mode 100644 index 0000000000..e1e7b28a29 --- /dev/null +++ b/engines/sword25/util/lua/lzio.cpp @@ -0,0 +1,82 @@ +/* +** $Id$ +** a generic input stream interface +** See Copyright Notice in lua.h +*/ + + +#include <string.h> + +#define lzio_c +#define LUA_CORE + +#include "lua.h" + +#include "llimits.h" +#include "lmem.h" +#include "lstate.h" +#include "lzio.h" + + +int luaZ_fill (ZIO *z) { + size_t size; + lua_State *L = z->L; + const char *buff; + lua_unlock(L); + buff = z->reader(L, z->data, &size); + lua_lock(L); + if (buff == NULL || size == 0) return EOZ; + z->n = size - 1; + z->p = buff; + return char2int(*(z->p++)); +} + + +int luaZ_lookahead (ZIO *z) { + if (z->n == 0) { + if (luaZ_fill(z) == EOZ) + return EOZ; + else { + z->n++; /* luaZ_fill removed first byte; put back it */ + z->p--; + } + } + return char2int(*z->p); +} + + +void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data) { + z->L = L; + z->reader = reader; + z->data = data; + z->n = 0; + z->p = NULL; +} + + +/* --------------------------------------------------------------- read --- */ +size_t luaZ_read (ZIO *z, void *b, size_t n) { + while (n) { + size_t m; + if (luaZ_lookahead(z) == EOZ) + return n; /* return number of missing bytes */ + m = (n <= z->n) ? n : z->n; /* min. between n and z->n */ + memcpy(b, z->p, m); + z->n -= m; + z->p += m; + b = (char *)b + m; + n -= m; + } + return 0; +} + +/* ------------------------------------------------------------------------ */ +char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n) { + if (n > buff->buffsize) { + if (n < LUA_MINBUFFER) n = LUA_MINBUFFER; + luaZ_resizebuffer(L, buff, n); + } + return buff->buffer; +} + + diff --git a/engines/sword25/util/lua/lzio.h b/engines/sword25/util/lua/lzio.h new file mode 100644 index 0000000000..9aa9e4b537 --- /dev/null +++ b/engines/sword25/util/lua/lzio.h @@ -0,0 +1,67 @@ +/* +** $Id$ +** Buffered streams +** See Copyright Notice in lua.h +*/ + + +#ifndef lzio_h +#define lzio_h + +#include "lua.h" + +#include "lmem.h" + + +#define EOZ (-1) /* end of stream */ + +typedef struct Zio ZIO; + +#define char2int(c) cast(int, cast(unsigned char, (c))) + +#define zgetc(z) (((z)->n--)>0 ? char2int(*(z)->p++) : luaZ_fill(z)) + +typedef struct Mbuffer { + char *buffer; + size_t n; + size_t buffsize; +} Mbuffer; + +#define luaZ_initbuffer(L, buff) ((buff)->buffer = NULL, (buff)->buffsize = 0) + +#define luaZ_buffer(buff) ((buff)->buffer) +#define luaZ_sizebuffer(buff) ((buff)->buffsize) +#define luaZ_bufflen(buff) ((buff)->n) + +#define luaZ_resetbuffer(buff) ((buff)->n = 0) + + +#define luaZ_resizebuffer(L, buff, size) \ + (luaM_reallocvector(L, (buff)->buffer, (buff)->buffsize, size, char), \ + (buff)->buffsize = size) + +#define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) + + +LUAI_FUNC char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n); +LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, + void *data); +LUAI_FUNC size_t luaZ_read (ZIO* z, void* b, size_t n); /* read next n bytes */ +LUAI_FUNC int luaZ_lookahead (ZIO *z); + + + +/* --------- Private Part ------------------ */ + +struct Zio { + size_t n; /* bytes still unread */ + const char *p; /* current position in buffer */ + lua_Reader reader; + void* data; /* additional data */ + lua_State *L; /* Lua state (for reader) */ +}; + + +LUAI_FUNC int luaZ_fill (ZIO *z); + +#endif diff --git a/engines/sword25/util/lua/print.cpp b/engines/sword25/util/lua/print.cpp new file mode 100644 index 0000000000..22039c9861 --- /dev/null +++ b/engines/sword25/util/lua/print.cpp @@ -0,0 +1,227 @@ +/* +** $Id$ +** print bytecodes +** See Copyright Notice in lua.h +*/ + +#include <ctype.h> +#include <stdio.h> + +#define luac_c +#define LUA_CORE + +#include "ldebug.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lundump.h" + +#define PrintFunction luaU_print + +#define Sizeof(x) ((int)sizeof(x)) +#define VOID(p) ((const void*)(p)) + +static void PrintString(const TString* ts) +{ + const char* s=getstr(ts); + size_t i,n=ts->tsv.len; + putchar('"'); + for (i=0; i<n; i++) + { + int c=s[i]; + switch (c) + { + case '"': printf("\\\""); break; + case '\\': printf("\\\\"); break; + case '\a': printf("\\a"); break; + case '\b': printf("\\b"); break; + case '\f': printf("\\f"); break; + case '\n': printf("\\n"); break; + case '\r': printf("\\r"); break; + case '\t': printf("\\t"); break; + case '\v': printf("\\v"); break; + default: if (isprint((unsigned char)c)) + putchar(c); + else + printf("\\%03u",(unsigned char)c); + } + } + putchar('"'); +} + +static void PrintConstant(const Proto* f, int i) +{ + const TValue* o=&f->k[i]; + switch (ttype(o)) + { + case LUA_TNIL: + printf("nil"); + break; + case LUA_TBOOLEAN: + printf(bvalue(o) ? "true" : "false"); + break; + case LUA_TNUMBER: + printf(LUA_NUMBER_FMT,nvalue(o)); + break; + case LUA_TSTRING: + PrintString(rawtsvalue(o)); + break; + default: /* cannot happen */ + printf("? type=%d",ttype(o)); + break; + } +} + +static void PrintCode(const Proto* f) +{ + const Instruction* code=f->code; + int pc,n=f->sizecode; + for (pc=0; pc<n; pc++) + { + Instruction i=code[pc]; + OpCode o=GET_OPCODE(i); + int a=GETARG_A(i); + int b=GETARG_B(i); + int c=GETARG_C(i); + int bx=GETARG_Bx(i); + int sbx=GETARG_sBx(i); + int line=getline(f,pc); + printf("\t%d\t",pc+1); + if (line>0) printf("[%d]\t",line); else printf("[-]\t"); + printf("%-9s\t",luaP_opnames[o]); + switch (getOpMode(o)) + { + case iABC: + printf("%d",a); + if (getBMode(o)!=OpArgN) printf(" %d",ISK(b) ? (-1-INDEXK(b)) : b); + if (getCMode(o)!=OpArgN) printf(" %d",ISK(c) ? (-1-INDEXK(c)) : c); + break; + case iABx: + if (getBMode(o)==OpArgK) printf("%d %d",a,-1-bx); else printf("%d %d",a,bx); + break; + case iAsBx: + if (o==OP_JMP) printf("%d",sbx); else printf("%d %d",a,sbx); + break; + } + switch (o) + { + case OP_LOADK: + printf("\t; "); PrintConstant(f,bx); + break; + case OP_GETUPVAL: + case OP_SETUPVAL: + printf("\t; %s", (f->sizeupvalues>0) ? getstr(f->upvalues[b]) : "-"); + break; + case OP_GETGLOBAL: + case OP_SETGLOBAL: + printf("\t; %s",svalue(&f->k[bx])); + break; + case OP_GETTABLE: + case OP_SELF: + if (ISK(c)) { printf("\t; "); PrintConstant(f,INDEXK(c)); } + break; + case OP_SETTABLE: + case OP_ADD: + case OP_SUB: + case OP_MUL: + case OP_DIV: + case OP_POW: + case OP_EQ: + case OP_LT: + case OP_LE: + if (ISK(b) || ISK(c)) + { + printf("\t; "); + if (ISK(b)) PrintConstant(f,INDEXK(b)); else printf("-"); + printf(" "); + if (ISK(c)) PrintConstant(f,INDEXK(c)); else printf("-"); + } + break; + case OP_JMP: + case OP_FORLOOP: + case OP_FORPREP: + printf("\t; to %d",sbx+pc+2); + break; + case OP_CLOSURE: + printf("\t; %p",VOID(f->p[bx])); + break; + case OP_SETLIST: + if (c==0) printf("\t; %d",(int)code[++pc]); + else printf("\t; %d",c); + break; + default: + break; + } + printf("\n"); + } +} + +#define SS(x) (x==1)?"":"s" +#define S(x) x,SS(x) + +static void PrintHeader(const Proto* f) +{ + const char* s=getstr(f->source); + if (*s=='@' || *s=='=') + s++; + else if (*s==LUA_SIGNATURE[0]) + s="(bstring)"; + else + s="(string)"; + printf("\n%s <%s:%d,%d> (%d instruction%s, %d bytes at %p)\n", + (f->linedefined==0)?"main":"function",s, + f->linedefined,f->lastlinedefined, + S(f->sizecode),f->sizecode*Sizeof(Instruction),VOID(f)); + printf("%d%s param%s, %d slot%s, %d upvalue%s, ", + f->numparams,f->is_vararg?"+":"",SS(f->numparams), + S(f->maxstacksize),S(f->nups)); + printf("%d local%s, %d constant%s, %d function%s\n", + S(f->sizelocvars),S(f->sizek),S(f->sizep)); +} + +static void PrintConstants(const Proto* f) +{ + int i,n=f->sizek; + printf("constants (%d) for %p:\n",n,VOID(f)); + for (i=0; i<n; i++) + { + printf("\t%d\t",i+1); + PrintConstant(f,i); + printf("\n"); + } +} + +static void PrintLocals(const Proto* f) +{ + int i,n=f->sizelocvars; + printf("locals (%d) for %p:\n",n,VOID(f)); + for (i=0; i<n; i++) + { + printf("\t%d\t%s\t%d\t%d\n", + i,getstr(f->locvars[i].varname),f->locvars[i].startpc+1,f->locvars[i].endpc+1); + } +} + +static void PrintUpvalues(const Proto* f) +{ + int i,n=f->sizeupvalues; + printf("upvalues (%d) for %p:\n",n,VOID(f)); + if (f->upvalues==NULL) return; + for (i=0; i<n; i++) + { + printf("\t%d\t%s\n",i,getstr(f->upvalues[i])); + } +} + +void PrintFunction(const Proto* f, int full) +{ + int i,n=f->sizep; + PrintHeader(f); + PrintCode(f); + if (full) + { + PrintConstants(f); + PrintLocals(f); + PrintUpvalues(f); + } + for (i=0; i<n; i++) PrintFunction(f->p[i],full); +} diff --git a/engines/sword25/util/pluto/CHANGELOG b/engines/sword25/util/pluto/CHANGELOG new file mode 100644 index 0000000000..e31ed26044 --- /dev/null +++ b/engines/sword25/util/pluto/CHANGELOG @@ -0,0 +1,38 @@ +$Id$ + +-- 2.4 -- +* Changed upval unboxing to allow upvals which contain func-housed cycles +* Added stack checking to all stack-growing functions +* Serialized debug information for functions + +-- 2.3 -- +* Added LUALIB_API declaration for luaopen_pluto + +-- 2.2 -- +* Rolled all internal Lua dependencies into the Pluto distribution +* Made the unit tests depend on dynamically loading Pluto + +-- 2.1 -- +* Various fixes to make the GC happy +* stack size always expanded where necessary +* fixed some memory leaks +* GC disabled during unpersist +* callstack initialized for traversal + +This changelog is maintained as of version 2.0alpha1. +Earlier versions are changelogged on the LuaForge site. + +-- 2.0 -- +* Fixed a few format changes to 5.1.3 +* Fixed myriad warnings +* GCC compliance: not incrementing cast results +* Fix for self-referring upvals +* Renamed loading function to work with Lua module system +* Loading tables with __newindex works +* unpersist makes buffer copy + +-- 2.0alpha1 -- +* Fixed all outstanding 5.0->5.1 conversion issues +* Made heavier use of size_t in preference to int +* Fixed GC/Upval issue (thanks to Eric Jacobs) + diff --git a/engines/sword25/util/pluto/FILEFORMAT b/engines/sword25/util/pluto/FILEFORMAT new file mode 100644 index 0000000000..b3f10ee603 --- /dev/null +++ b/engines/sword25/util/pluto/FILEFORMAT @@ -0,0 +1,168 @@ +$Id$ + +pluto_persist() produces a "hunk" of objects. Here's the file format adhered +to by the function, and expected by pluto_unpersist(). + +As a developer, I feel that where file format information is given it is of +utmost importance that that information precisely and accurately reflects the +actual operation of the application. Therefore, if you find any discrepancy +between this and actual operation, please lambast me thoroughly over email. + +Pseudo-C is used to express the file format. Padding is assumed to be +nonexistent. The keyword "one_of" is used to express a concept similar to +"union", except that its size is the size of the actual datatype chosen. Thus, +objects which contain, directly or indirectly, a one_of, may vary in size. + + +struct Object { + int firstTime; /* Whether this is the first time the object + is being referenced */ + one_of { + RealObject o; /* if firstTime == 1 */ + Reference r; /* if firstTime == 0 */ + }; +}; + +struct Reference { + int ref; /* The index the object was registered with */ +}; + +struct RealObject { + int type; /* The type of the object */ + one_of { + Boolean b; /* If type == LUA_TBOOLEAN */ + LightUserData l; /* If type == LUA_TLIGHTUSERDATA */ + Number n; /* If type == LUA_TNUMBER */ + String s; /* If type == LUA_TSTRING */ + Table t; /* If type == LUA_TTABLE */ + Function f; /* If type == LUA_TFUNCTION */ + Userdata u; /* If type == LUA_TUSERDATA */ + Thread th; /* If type == LUA_TTHREAD */ + Proto p; /* If type == LUA_TPROTO (from lobject.h) */ + Upval uv; /* If type == LUA_TUPVAL (from lobject.h) */ + }; /* The actual object */ +}; + +struct Boolean { + int32 bvalue; /* 0 for false, 1 for true */ +}; + +struct LightUserData { + void* luvalue; /* The actual, literal pointer */ +}; + +struct Number { + lua_Number nvalue; /* The actual number */ +}; + +struct String { + int length; /* The length of the string */ + char str[length]; /* The actual string (not null terminated) */ +}; + +struct Table { + int isspecial; /* 1 if SP is used; 0 otherwise */ + one_of { + Closure c; /* if isspecial == 1; closure to refill the table */ + LiteralTable t; /* if isspecial == 0; literal table info */ + }; +}; + +struct LiteralTable { + Object metatable; /* nil for default metatable */ + Pair p[]; /* key/value pairs */ + Object nil = nil; /* Nil reference to terminate */ +}; + +struct Pair { + Object key; + Object value; +}; + +struct Function { /* Actually a closure */ + lu_byte nups; /* Number of upvalues the function uses */ + Object proto; /* The proto this function uses */ + Object upvals[nups]; /* All upvalues */ + Object fenv; /* The FEnv (nil for the global table) +}; + +struct Upval { + Object obj; /* The object this upval refers to */ +} + +struct Userdata { + int isSpecial; /* 1 for special persistence, 0 for literal + one_of { + LiteralUserdata lu; /* if is_special is 0 */ + SpecialUserdata su; /* if is_special is 1 */ + }; +}; + +struct LiteralUserdata { + Object metatable; /* The metatable (nil for default) */ + int length; /* Size of the data */ + char data[length]; /* The actual data */ +}; + +struct SpecialUserdata { + int length; /* The size of the data */ + Object func; /* The closure used to fill the userdata */ +}; + +struct Thread { + int stacksize; /* The size of the stack filled with objects, + * including the "nil" that is hidden below + * the bottom of the stack visible to C */ + Object stack[stacksize];/* Indices of all stack values, bottom up */ + int callinfosize; /* Number of elements in the CallInfo stack */ + CallInfo callinfostack[callinfosize]; /* The CallInfo stack */ + int base; /* base = L->base - L->stack; */ + int top; /* top = L->top - L->stack; */ + OpenUpval openupvals[]; /* Upvalues to open */ + Object nil = nil; /* To terminate the open upvalues list */ +}; + +struct OpenUpval { + Object upval; /* The upvalue */ + int stackpos; /* The stack position to "reopen" it to */ + +}; + +struct CallInfo { + int base; /* base = ci->base - L->stack; */ + int top; /* top = ci->top - L->stack; */ + int pc; /* pc = ci->pc - proto->code; */ + int state; /* flags used by the CallInfo */ +}; + +struct Proto { + int sizek; /* Number of constants referenced */ + Object k[sizek]; /* Constants referenced */ + int sizep; /* Number of inner Protos referenced */ + Object p[sizep]; /* Inner Protos referenced */ + int sizecode; /* Number of instructions in code */ + Instruction code[sizecode]; /* The proto's code */ + ProtoDebug debuginfo; /* Debug information for the proto */ + lu_byte nups; /* Number of upvalues used */ + lu_byte numparams; /* Number of parameters taken */ + lu_byte is_vararg; /* 1 if function accepts varargs, 0 otherwise */ + lu_byte maxstacksize; /* Size of stack reserved for the function */ +}; + +struct ProtoDebug { + int sizeupvals; /* Number of upvalue names */ + Object upvals; /* Upvalue names */ + int sizelocvars; /* Number of local variable names */ + LocVar[sizelocvars]; /* Local variable names */ + Object source; /* The source code */ + int sizelineinfo; /* Number of opcode-line mappings */ + int lineinfo[sizelineinfo]; /* opcode-line mappings */ + int linedefined; /* Start of line range */ + int lastlinedefined; /* End of line range */ +}; + +struct LocVar { + Object name; /* Name of the local variable */ + int startpc; /* Point where variable is active */ + int endpc; /* Point where variable is dead */ +};
\ No newline at end of file diff --git a/engines/sword25/util/pluto/Makefile b/engines/sword25/util/pluto/Makefile new file mode 100644 index 0000000000..611ecc83d2 --- /dev/null +++ b/engines/sword25/util/pluto/Makefile @@ -0,0 +1,29 @@ +LDLIBS= -lm -ldl -llua +LDFLAGS = -rdynamic # -L../lua-5.1.3/src +# CFLAGS= -g3 -Wall -fprofile-arcs -ftest-coverage +CFLAGS= -g3 -Wall -ansi -pedantic + +LIBTOOL=libtool --tag=CC + +default: pluto.so pptest puptest + +%.lo: %.c + $(LIBTOOL) --mode=compile cc $(CFLAGS) -c $< + +pluto.so: pluto.lo pdep.lo lzio.lo + $(LIBTOOL) --mode=link cc -rpath /usr/local/lib/lua/5.1 -o libpluto.la $^ + mv .libs/libpluto.so.0.0.0 $@ + +test: pptest puptest pptest.lua puptest.lua pluto.so + ./pptest + ./puptest + +pptest: pptest.o + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +puptest: puptest.o + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +clean: + -rm -r *.so *.la *.lo .libs *.a *.o *.bb *.bbg *.da *.gcov pptest puptest test.plh + diff --git a/engines/sword25/util/pluto/README b/engines/sword25/util/pluto/README new file mode 100644 index 0000000000..838fce498b --- /dev/null +++ b/engines/sword25/util/pluto/README @@ -0,0 +1,133 @@ +$Id$ + +PLUTO - Heavy duty persistence for Lua + +Pluto is a library which allows users to write arbitrarily large portions +of the "Lua universe" into a flat file, and later read them back into the +same or a different Lua universe. Object references are appropriately +handled, such that the file contains everything needed to recreate the +objects in question. + +Pluto has the following major features: +* Can persist any Lua function +* Can persist threads +* Works with any Lua chunkreader/chunkwriter +* Support for "invariant" permanent objects, of all datatypes +* Can invoke metafunctions for custom persistence of tables and userdata + +Pluto 2.2 requires Lua 5.1.3. If you need to use Pluto with Lua +5.0, please use version 1.2 of Pluto. + +Starting with version 2.2, Pluto no longer depends on the Lua sources. +Instead, it subsumes the required headers into its own codebase. +As a result, it may not work properly with Lua version 5.1.4 or later. + +Pluto may have bugs. Users are advised to define lua_assert in +luaconf.h to something useful when compiling in debug mode, to catch +assertions by Pluto and Lua. + +The Pluto library consists of two public functions. + +int pluto_persist(lua_State *L, lua_Chunkwriter writer, void *ud) + +This function recursively persists the Lua object in stack position 2 +and all other objects which are directly or indirectly referenced by +it, except those referenced in the permanent object table. The data +is written using the chunk-writer given, and that writer is passed +the arbitrary pointer value ud. + +The Lua stack must contain exactly and only these two items, in order: + +1. A table of permanent objects, that should not be persisted. For each +permanent object, the object itself should be the key, and a unique +object of any type should be the value. Likely candidates for this table +include Lua functions (including those in the Lua libraries) that are +loaded at load-time. It must include all non-persistable objects that +are referenced by the object to be persisted. The table is not modified +by the function. Objects in this table are considered "opaque" and are +not examined or descended into. Objects should not appear in the table +multiple times; the result of doing this is undefined (though probably +harmless). NOTE: If you are planning to persist threads, keep in mind +that all yielded threads have coroutine.yield on the tops of their +stacks. Since it's a C function, it should be put here. For complex +permanents, it may be a good idea to use the __index meta-function of +the permanents table to "search" for permanents. + +2. The single object to be persisted. In many cases, this will be the +global table. For more flexibility, however, it may be something like a +table built for the occasion, with various values to keep track of. The +object may not be nil. + + +int pluto_unpersist(lua_State *L, lua_Chunkreader reader, void *ud) + +This function loads in a Lua object and places it on top of the stack. All +objects directly or indirectly referenced by it are also loaded. + +The Lua stack must contain, as its top value, a table of permanent +objects. This table should be like the permanent object table used when +persisting, but with the key and value of each pair reversed. These +objects are used as substitutes for those referenced in their positions +when persisting, and under most circumstances should be identical objects +to those referenced in the permanents table used for persisting. It's +okay for multiple keys to refer to the same object. + + +RUNNING PLUTO FROM LUA: +It is also possible to invoke pluto from a Lua script. The C function +pluto_open() will register pluto.persist and pluto.unpersist, lua functions +which operate on strings. The first takes a permanents table and a root +object, and returns a string; the second takes a permanents table and a +string, and returns the root object. + +An error will be raised if pluto.persist is called from a thread which is +itself referenced by the root object. + +SPECIAL PERSISTENCE: +Tables and userdata have special persistence semantics. These semantics are +keyed to the value of the object's metatable's __persist member, if any. This +member may be any of the following four values: +1. Boolean "true": The table or userdata is persisted literally; tables are +persisted member-by-member, and userdata are written out as literal data. +2. Boolean "false": An error is returned, indicating that the object cannot +be persisted. +3. A function: This function should take one argument, the object in question, +and return one result, a closure. This "fixup closure", in turn, will be +persisted, and during unpersistence will be called. The closure will be +responsible for recreating the object with the appropriate data, based on +its upvalues. +4. Nil, or no metatable. In the case of tables, the table is literally +persisted. In the case of userdata, an error is returned. + +Here's an example of special persistence for a simple 3d vector object: + +vec = { x = 2, y = 1, z = 4 } +setmetatable(vec, { __persist = function(oldtbl) + local x = oldtbl.x + local y = oldtbl.y + local z = oldtbl.z + local mt = getmetatable(oldtbl) + return function() + newtbl = {} + newtbl.x = x + newtbl.y = y + newtbl.z = z + setmetatable(newtbl, mt) + return newtbl + end +end }) + +Note how x, y, z, and the mt are explicitly pulled out of the table. It is +important that the fixup closure returned not reference the original table +directly, as that table would again be persisted as an upvalue, leading to an +infinite loop. Also note that the object's metatable is NOT automatically +persisted; it is necessary for the fixup closure to reset it, if it wants. + +LIMITATIONS/TODO: +* Light userdata are persisted literally, as their pointer values. This +may or may not be what you want. +* Closures of C functions may not be persisted. Once it becomes possible +to specify a C function "proto" as a permanent object, this restriction +will be relaxed. + +BUGS: None known. Emphasis on the 'known'. diff --git a/engines/sword25/util/pluto/THANKS b/engines/sword25/util/pluto/THANKS new file mode 100644 index 0000000000..fea3595dbf --- /dev/null +++ b/engines/sword25/util/pluto/THANKS @@ -0,0 +1,10 @@ +Pluto is surprisingly robust and useful. This would not be the case without +the hard work and helpfulness of the following people, mentioned in no +particular order: + +Ivko Stanilov +Goran Adrinek +Eric Jacobs +Anolan Milanes +Malte Thiesen + diff --git a/engines/sword25/util/pluto/pdep.cpp b/engines/sword25/util/pluto/pdep.cpp new file mode 100644 index 0000000000..a32c43b42d --- /dev/null +++ b/engines/sword25/util/pluto/pdep.cpp @@ -0,0 +1,112 @@ +/* This file is derived from the Lua source code. Please see lua.h for +the copyright statement. +*/ + +#include "pdep/pdep.h" + +#define api_incr_top(L) {api_check(L, L->top < L->ci->top); L->top++;} + +void pdep_pushobject (lua_State *L, const TValue *o) { + setobj2s(L, L->top, o); + api_incr_top(L); +} + +void *pdep_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { + global_State *g = G(L); + lua_assert((osize == 0) == (block == NULL)); + block = (*g->frealloc)(g->ud, block, osize, nsize); + lua_assert((nsize == 0) == (block == NULL)); + g->totalbytes = (g->totalbytes - osize) + nsize; + return block; +} + +void pdep_link (lua_State *L, GCObject *o, lu_byte tt) { + global_State *g = G(L); + o->gch.next = g->rootgc; + g->rootgc = o; + o->gch.marked = luaC_white(g); + o->gch.tt = tt; +} + +Proto *pdep_newproto (lua_State *L) { + Proto *f = pdep_new(L, Proto); + pdep_link(L, obj2gco(f), LUA_TPROTO); + f->k = NULL; + f->sizek = 0; + f->p = NULL; + f->sizep = 0; + f->code = NULL; + f->sizecode = 0; + f->sizelineinfo = 0; + f->sizeupvalues = 0; + f->nups = 0; + f->upvalues = NULL; + f->numparams = 0; + f->is_vararg = 0; + f->maxstacksize = 0; + f->lineinfo = NULL; + f->sizelocvars = 0; + f->locvars = NULL; + f->linedefined = 0; + f->lastlinedefined = 0; + f->source = NULL; + return f; +} + +Closure *pdep_newLclosure (lua_State *L, int nelems, Table *e) { + Closure *c = cast(Closure *, pdep_malloc(L, sizeLclosure(nelems))); + pdep_link(L, obj2gco(c), LUA_TFUNCTION); + c->l.isC = 0; + c->l.env = e; + c->l.nupvalues = cast_byte(nelems); + while (nelems--) c->l.upvals[nelems] = NULL; + return c; +} + +static void correctstack (lua_State *L, TValue *oldstack) { + CallInfo *ci; + GCObject *up; + L->top = (L->top - oldstack) + L->stack; + for (up = L->openupval; up != NULL; up = up->gch.next) + gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack; + for (ci = L->base_ci; ci <= L->ci; ci++) { + ci->top = (ci->top - oldstack) + L->stack; + ci->base = (ci->base - oldstack) + L->stack; + ci->func = (ci->func - oldstack) + L->stack; + } + L->base = (L->base - oldstack) + L->stack; +} + + +void pdep_reallocstack (lua_State *L, int newsize) { + TValue *oldstack = L->stack; + int realsize = newsize + 1 + EXTRA_STACK; + lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); + pdep_reallocvector(L, L->stack, L->stacksize, realsize, TValue); + L->stacksize = realsize; + L->stack_last = L->stack+newsize; + correctstack(L, oldstack); +} + +void pdep_growstack (lua_State *L, int n) { + if (n <= L->stacksize) /* double size is enough? */ + pdep_reallocstack(L, 2*L->stacksize); + else + pdep_reallocstack(L, L->stacksize + n); +} + +void pdep_reallocCI (lua_State *L, int newsize) { + CallInfo *oldci = L->base_ci; + pdep_reallocvector(L, L->base_ci, L->size_ci, newsize, CallInfo); + L->size_ci = newsize; + L->ci = (L->ci - oldci) + L->base_ci; + L->end_ci = L->base_ci + L->size_ci - 1; +} + +TString *pdep_newlstr (lua_State *L, const char *str, size_t l) { + TString *res; + lua_pushlstring(L, str, l); + res = rawtsvalue(L->top-1); + lua_pop(L, 1); + return res; +} diff --git a/engines/sword25/util/pluto/pdep/README b/engines/sword25/util/pluto/pdep/README new file mode 100644 index 0000000000..3592754da0 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/README @@ -0,0 +1,5 @@ +These files are directly copied from the Lua distribution, with the +exception of lzio.h, which is s/lua{ZM}/pdep/g and has an include removed. + +As such, unlike the rest of Pluto, they are released under the +same terms as Lua. See "lua.h" for the copyright notice. diff --git a/engines/sword25/util/pluto/pdep/lauxlib.h b/engines/sword25/util/pluto/pdep/lauxlib.h new file mode 100644 index 0000000000..d58f290527 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/lauxlib.h @@ -0,0 +1,174 @@ +/* +** $Id$ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lauxlib_h +#define lauxlib_h + + +#include <stddef.h> +#include <stdio.h> + +#include "lua.h" + + +#if defined(LUA_COMPAT_GETN) +LUALIB_API int (luaL_getn) (lua_State *L, int t); +LUALIB_API void (luaL_setn) (lua_State *L, int t, int n); +#else +#define luaL_getn(L,i) ((int)lua_objlen(L, i)) +#define luaL_setn(L,i,j) ((void)0) /* no op! */ +#endif + +#if defined(LUA_COMPAT_OPENLIB) +#define luaI_openlib luaL_openlib +#endif + + +/* extra error code for `luaL_load' */ +#define LUA_ERRFILE (LUA_ERRERR+1) + + +typedef struct luaL_Reg { + const char *name; + lua_CFunction func; +} luaL_Reg; + + + +LUALIB_API void (luaI_openlib) (lua_State *L, const char *libname, + const luaL_Reg *l, int nup); +LUALIB_API void (luaL_register) (lua_State *L, const char *libname, + const luaL_Reg *l); +LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_typerror) (lua_State *L, int narg, const char *tname); +LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg, + size_t *l); +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg, + const char *def, size_t *l); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def); + +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg, + lua_Integer def); + +LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); +LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int narg); + +LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); +LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); + +LUALIB_API void (luaL_where) (lua_State *L, int lvl); +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); + +LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def, + const char *const lst[]); + +LUALIB_API int (luaL_ref) (lua_State *L, int t); +LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); + +LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename); +LUALIB_API int (luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz, + const char *name); +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); + +LUALIB_API lua_State *(luaL_newstate) (void); + + +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, + const char *r); + +LUALIB_API const char *(luaL_findtable) (lua_State *L, int idx, + const char *fname, int szhint); + + + + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + +#define luaL_argcheck(L, cond,numarg,extramsg) \ + ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) +#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) + +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) + +#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + + + +typedef struct luaL_Buffer { + char *p; /* current position in buffer */ + int lvl; /* number of strings in the stack (level) */ + lua_State *L; + char buffer[LUAL_BUFFERSIZE]; +} luaL_Buffer; + +#define luaL_addchar(B,c) \ + ((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \ + (*(B)->p++ = (char)(c))) + +/* compatibility only */ +#define luaL_putchar(B,c) luaL_addchar(B,c) + +#define luaL_addsize(B,n) ((B)->p += (n)) + +LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffer) (luaL_Buffer *B); +LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); +LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); + + +/* }====================================================== */ + + +/* compatibility with ref system */ + +/* pre-defined references */ +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) + +#define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \ + (lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0)) + +#define lua_unref(L,ref) luaL_unref(L, LUA_REGISTRYINDEX, (ref)) + +#define lua_getref(L,ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref)) + + +#define luaL_reg luaL_Reg + +#endif + + diff --git a/engines/sword25/util/pluto/pdep/ldo.h b/engines/sword25/util/pluto/pdep/ldo.h new file mode 100644 index 0000000000..4c97134805 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/ldo.h @@ -0,0 +1,57 @@ +/* +** $Id$ +** Stack and Call structure of Lua +** See Copyright Notice in lua.h +*/ + +#ifndef ldo_h +#define ldo_h + + +#include "lobject.h" +#include "lstate.h" +#include "lzio.h" + + +#define luaD_checkstack(L,n) \ + if ((char *)L->stack_last - (char *)L->top <= (n)*(int)sizeof(TValue)) \ + luaD_growstack(L, n); \ + else condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); + + +#define incr_top(L) {luaD_checkstack(L,1); L->top++;} + +#define savestack(L,p) ((char *)(p) - (char *)L->stack) +#define restorestack(L,n) ((TValue *)((char *)L->stack + (n))) + +#define saveci(L,p) ((char *)(p) - (char *)L->base_ci) +#define restoreci(L,n) ((CallInfo *)((char *)L->base_ci + (n))) + + +/* results from luaD_precall */ +#define PCRLUA 0 /* initiated a call to a Lua function */ +#define PCRC 1 /* did a call to a C function */ +#define PCRYIELD 2 /* C funtion yielded */ + + +/* type of protected functions, to be ran by `runprotected' */ +typedef void (*Pfunc) (lua_State *L, void *ud); + +LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name); +LUAI_FUNC void luaD_callhook (lua_State *L, int event, int line); +LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults); +LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); +LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, + ptrdiff_t oldtop, ptrdiff_t ef); +LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult); +LUAI_FUNC void luaD_reallocCI (lua_State *L, int newsize); +LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize); +LUAI_FUNC void luaD_growstack (lua_State *L, int n); + +LUAI_FUNC void luaD_throw (lua_State *L, int errcode); +LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); + +LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop); + +#endif + diff --git a/engines/sword25/util/pluto/pdep/lfunc.h b/engines/sword25/util/pluto/pdep/lfunc.h new file mode 100644 index 0000000000..4c2b7fd138 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/lfunc.h @@ -0,0 +1,34 @@ +/* +** $Id$ +** Auxiliary functions to manipulate prototypes and closures +** See Copyright Notice in lua.h +*/ + +#ifndef lfunc_h +#define lfunc_h + + +#include "lobject.h" + + +#define sizeCclosure(n) (cast(int, sizeof(CClosure)) + \ + cast(int, sizeof(TValue)*((n)-1))) + +#define sizeLclosure(n) (cast(int, sizeof(LClosure)) + \ + cast(int, sizeof(TValue *)*((n)-1))) + + +LUAI_FUNC Proto *luaF_newproto (lua_State *L); +LUAI_FUNC Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e); +LUAI_FUNC Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e); +LUAI_FUNC UpVal *luaF_newupval (lua_State *L); +LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); +LUAI_FUNC void luaF_close (lua_State *L, StkId level); +LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); +LUAI_FUNC void luaF_freeclosure (lua_State *L, Closure *c); +LUAI_FUNC void luaF_freeupval (lua_State *L, UpVal *uv); +LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, + int pc); + + +#endif diff --git a/engines/sword25/util/pluto/pdep/lgc.h b/engines/sword25/util/pluto/pdep/lgc.h new file mode 100644 index 0000000000..5123ccb479 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/lgc.h @@ -0,0 +1,110 @@ +/* +** $Id$ +** Garbage Collector +** See Copyright Notice in lua.h +*/ + +#ifndef lgc_h +#define lgc_h + + +#include "lobject.h" + + +/* +** Possible states of the Garbage Collector +*/ +#define GCSpause 0 +#define GCSpropagate 1 +#define GCSsweepstring 2 +#define GCSsweep 3 +#define GCSfinalize 4 + + +/* +** some userful bit tricks +*/ +#define resetbits(x,m) ((x) &= cast(lu_byte, ~(m))) +#define setbits(x,m) ((x) |= (m)) +#define testbits(x,m) ((x) & (m)) +#define bitmask(b) (1<<(b)) +#define bit2mask(b1,b2) (bitmask(b1) | bitmask(b2)) +#define l_setbit(x,b) setbits(x, bitmask(b)) +#define resetbit(x,b) resetbits(x, bitmask(b)) +#define testbit(x,b) testbits(x, bitmask(b)) +#define set2bits(x,b1,b2) setbits(x, (bit2mask(b1, b2))) +#define reset2bits(x,b1,b2) resetbits(x, (bit2mask(b1, b2))) +#define test2bits(x,b1,b2) testbits(x, (bit2mask(b1, b2))) + + + +/* +** Layout for bit use in `marked' field: +** bit 0 - object is white (type 0) +** bit 1 - object is white (type 1) +** bit 2 - object is black +** bit 3 - for userdata: has been finalized +** bit 3 - for tables: has weak keys +** bit 4 - for tables: has weak values +** bit 5 - object is fixed (should not be collected) +** bit 6 - object is "super" fixed (only the main thread) +*/ + + +#define WHITE0BIT 0 +#define WHITE1BIT 1 +#define BLACKBIT 2 +#define FINALIZEDBIT 3 +#define KEYWEAKBIT 3 +#define VALUEWEAKBIT 4 +#define FIXEDBIT 5 +#define SFIXEDBIT 6 +#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) + + +#define iswhite(x) test2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT) +#define isblack(x) testbit((x)->gch.marked, BLACKBIT) +#define isgray(x) (!isblack(x) && !iswhite(x)) + +#define otherwhite(g) (g->currentwhite ^ WHITEBITS) +#define isdead(g,v) ((v)->gch.marked & otherwhite(g) & WHITEBITS) + +#define changewhite(x) ((x)->gch.marked ^= WHITEBITS) +#define gray2black(x) l_setbit((x)->gch.marked, BLACKBIT) + +#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) + +#define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS) + + +#define luaC_checkGC(L) { \ + condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); \ + if (G(L)->totalbytes >= G(L)->GCthreshold) \ + luaC_step(L); } + + +#define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ + luaC_barrierf(L,obj2gco(p),gcvalue(v)); } + +#define luaC_barriert(L,t,v) { if (valiswhite(v) && isblack(obj2gco(t))) \ + luaC_barrierback(L,t); } + +#define luaC_objbarrier(L,p,o) \ + { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \ + luaC_barrierf(L,obj2gco(p),obj2gco(o)); } + +#define luaC_objbarriert(L,t,o) \ + { if (iswhite(obj2gco(o)) && isblack(obj2gco(t))) luaC_barrierback(L,t); } + +LUAI_FUNC size_t luaC_separateudata (lua_State *L, int all); +LUAI_FUNC void luaC_callGCTM (lua_State *L); +LUAI_FUNC void luaC_freeall (lua_State *L); +LUAI_FUNC void luaC_step (lua_State *L); +LUAI_FUNC void luaC_fullgc (lua_State *L); +LUAI_FUNC void luaC_link (lua_State *L, GCObject *o, lu_byte tt); +LUAI_FUNC void luaC_linkupval (lua_State *L, UpVal *uv); +LUAI_FUNC void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v); +LUAI_FUNC void luaC_barrierback (lua_State *L, Table *t); + + +#endif diff --git a/engines/sword25/util/pluto/pdep/llimits.h b/engines/sword25/util/pluto/pdep/llimits.h new file mode 100644 index 0000000000..a31ad160ad --- /dev/null +++ b/engines/sword25/util/pluto/pdep/llimits.h @@ -0,0 +1,128 @@ +/* +** $Id$ +** Limits, basic types, and some other `installation-dependent' definitions +** See Copyright Notice in lua.h +*/ + +#ifndef llimits_h +#define llimits_h + + +#include <limits.h> +#include <stddef.h> + + +#include "lua.h" + + +typedef LUAI_UINT32 lu_int32; + +typedef LUAI_UMEM lu_mem; + +typedef LUAI_MEM l_mem; + + + +/* chars used as small naturals (so that `char' is reserved for characters) */ +typedef unsigned char lu_byte; + + +#define MAX_SIZET ((size_t)(~(size_t)0)-2) + +#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)-2) + + +#define MAX_INT (INT_MAX-2) /* maximum value of an int (-2 for safety) */ + +/* +** conversion of pointer to integer +** this is for hashing only; there is no problem if the integer +** cannot hold the whole pointer value +*/ +#define IntPoint(p) ((unsigned int)(lu_mem)(p)) + + + +/* type to ensure maximum alignment */ +typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; + + +/* result of a `usual argument conversion' over lua_Number */ +typedef LUAI_UACNUMBER l_uacNumber; + + +/* internal assertions for in-house debugging */ +#ifdef lua_assert + +#define check_exp(c,e) (lua_assert(c), (e)) +#define api_check(l,e) lua_assert(e) + +#else + +#define lua_assert(c) ((void)0) +#define check_exp(c,e) (e) +#define api_check luai_apicheck + +#endif + + +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) /* to avoid warnings */ +#endif + + +#ifndef cast +#define cast(t, exp) ((t)(exp)) +#endif + +#define cast_byte(i) cast(lu_byte, (i)) +#define cast_num(i) cast(lua_Number, (i)) +#define cast_int(i) cast(int, (i)) + + + +/* +** type for virtual-machine instructions +** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) +*/ +typedef lu_int32 Instruction; + + + +/* maximum stack for a Lua function */ +#define MAXSTACK 250 + + + +/* minimum size for the string table (must be power of 2) */ +#ifndef MINSTRTABSIZE +#define MINSTRTABSIZE 32 +#endif + + +/* minimum size for string buffer */ +#ifndef LUA_MINBUFFER +#define LUA_MINBUFFER 32 +#endif + + +#ifndef lua_lock +#define lua_lock(L) ((void) 0) +#define lua_unlock(L) ((void) 0) +#endif + +#ifndef luai_threadyield +#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} +#endif + + +/* +** macro to control inclusion of some hard tests on stack reallocation +*/ +#ifndef HARDSTACKTESTS +#define condhardstacktests(x) ((void)0) +#else +#define condhardstacktests(x) x +#endif + +#endif diff --git a/engines/sword25/util/pluto/pdep/lobject.h b/engines/sword25/util/pluto/pdep/lobject.h new file mode 100644 index 0000000000..35aaed028a --- /dev/null +++ b/engines/sword25/util/pluto/pdep/lobject.h @@ -0,0 +1,381 @@ +/* +** $Id$ +** Type definitions for Lua objects +** See Copyright Notice in lua.h +*/ + + +#ifndef lobject_h +#define lobject_h + + +#include <stdarg.h> + + +#include "llimits.h" +#include "lua.h" + + +/* tags for values visible from Lua */ +#define LAST_TAG LUA_TTHREAD + +#define NUM_TAGS (LAST_TAG+1) + + +/* +** Extra tags for non-values +*/ +#define LUA_TPROTO (LAST_TAG+1) +#define LUA_TUPVAL (LAST_TAG+2) +#define LUA_TDEADKEY (LAST_TAG+3) + + +/* +** Union of all collectable objects +*/ +typedef union GCObject GCObject; + + +/* +** Common Header for all collectable objects (in macro form, to be +** included in other objects) +*/ +#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked + + +/* +** Common header in struct form +*/ +typedef struct GCheader { + CommonHeader; +} GCheader; + + + + +/* +** Union of all Lua values +*/ +typedef union { + GCObject *gc; + void *p; + lua_Number n; + int b; +} Value; + + +/* +** Tagged Values +*/ + +#define TValuefields Value value; int tt + +typedef struct lua_TValue { + TValuefields; +} TValue; + + +/* Macros to test type */ +#define ttisnil(o) (ttype(o) == LUA_TNIL) +#define ttisnumber(o) (ttype(o) == LUA_TNUMBER) +#define ttisstring(o) (ttype(o) == LUA_TSTRING) +#define ttistable(o) (ttype(o) == LUA_TTABLE) +#define ttisfunction(o) (ttype(o) == LUA_TFUNCTION) +#define ttisboolean(o) (ttype(o) == LUA_TBOOLEAN) +#define ttisuserdata(o) (ttype(o) == LUA_TUSERDATA) +#define ttisthread(o) (ttype(o) == LUA_TTHREAD) +#define ttislightuserdata(o) (ttype(o) == LUA_TLIGHTUSERDATA) + +/* Macros to access values */ +#define ttype(o) ((o)->tt) +#define gcvalue(o) check_exp(iscollectable(o), (o)->value.gc) +#define pvalue(o) check_exp(ttislightuserdata(o), (o)->value.p) +#define nvalue(o) check_exp(ttisnumber(o), (o)->value.n) +#define rawtsvalue(o) check_exp(ttisstring(o), &(o)->value.gc->ts) +#define tsvalue(o) (&rawtsvalue(o)->tsv) +#define rawuvalue(o) check_exp(ttisuserdata(o), &(o)->value.gc->u) +#define uvalue(o) (&rawuvalue(o)->uv) +#define clvalue(o) check_exp(ttisfunction(o), &(o)->value.gc->cl) +#define hvalue(o) check_exp(ttistable(o), &(o)->value.gc->h) +#define bvalue(o) check_exp(ttisboolean(o), (o)->value.b) +#define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th) + +#define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0)) + +/* +** for internal debug only +*/ +#define checkconsistency(obj) \ + lua_assert(!iscollectable(obj) || (ttype(obj) == (obj)->value.gc->gch.tt)) + +#define checkliveness(g,obj) \ + lua_assert(!iscollectable(obj) || \ + ((ttype(obj) == (obj)->value.gc->gch.tt) && !isdead(g, (obj)->value.gc))) + + +/* Macros to set values */ +#define setnilvalue(obj) ((obj)->tt=LUA_TNIL) + +#define setnvalue(obj,x) \ + { TValue *i_o=(obj); i_o->value.n=(x); i_o->tt=LUA_TNUMBER; } + +#define setpvalue(obj,x) \ + { TValue *i_o=(obj); i_o->value.p=(x); i_o->tt=LUA_TLIGHTUSERDATA; } + +#define setbvalue(obj,x) \ + { TValue *i_o=(obj); i_o->value.b=(x); i_o->tt=LUA_TBOOLEAN; } + +#define setsvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TSTRING; \ + checkliveness(G(L),i_o); } + +#define setuvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TUSERDATA; \ + checkliveness(G(L),i_o); } + +#define setthvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTHREAD; \ + checkliveness(G(L),i_o); } + +#define setclvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TFUNCTION; \ + checkliveness(G(L),i_o); } + +#define sethvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTABLE; \ + checkliveness(G(L),i_o); } + +#define setptvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TPROTO; \ + checkliveness(G(L),i_o); } + + + + +#define setobj(L,obj1,obj2) \ + { const TValue *o2=(obj2); TValue *o1=(obj1); \ + o1->value = o2->value; o1->tt=o2->tt; \ + checkliveness(G(L),o1); } + + +/* +** different types of sets, according to destination +*/ + +/* from stack to (same) stack */ +#define setobjs2s setobj +/* to stack (not from same stack) */ +#define setobj2s setobj +#define setsvalue2s setsvalue +#define sethvalue2s sethvalue +#define setptvalue2s setptvalue +/* from table to same table */ +#define setobjt2t setobj +/* to table */ +#define setobj2t setobj +/* to new object */ +#define setobj2n setobj +#define setsvalue2n setsvalue + +#define setttype(obj, tt) (ttype(obj) = (tt)) + + +#define iscollectable(o) (ttype(o) >= LUA_TSTRING) + + + +typedef TValue *StkId; /* index to stack elements */ + + +/* +** String headers for string table +*/ +typedef union TString { + L_Umaxalign dummy; /* ensures maximum alignment for strings */ + struct { + CommonHeader; + lu_byte reserved; + unsigned int hash; + size_t len; + } tsv; +} TString; + + +#define getstr(ts) cast(const char *, (ts) + 1) +#define svalue(o) getstr(tsvalue(o)) + + + +typedef union Udata { + L_Umaxalign dummy; /* ensures maximum alignment for `local' udata */ + struct { + CommonHeader; + struct Table *metatable; + struct Table *env; + size_t len; + } uv; +} Udata; + + + + +/* +** Function Prototypes +*/ +typedef struct Proto { + CommonHeader; + TValue *k; /* constants used by the function */ + Instruction *code; + struct Proto **p; /* functions defined inside the function */ + int *lineinfo; /* map from opcodes to source lines */ + struct LocVar *locvars; /* information about local variables */ + TString **upvalues; /* upvalue names */ + TString *source; + int sizeupvalues; + int sizek; /* size of `k' */ + int sizecode; + int sizelineinfo; + int sizep; /* size of `p' */ + int sizelocvars; + int linedefined; + int lastlinedefined; + GCObject *gclist; + lu_byte nups; /* number of upvalues */ + lu_byte numparams; + lu_byte is_vararg; + lu_byte maxstacksize; +} Proto; + + +/* masks for new-style vararg */ +#define VARARG_HASARG 1 +#define VARARG_ISVARARG 2 +#define VARARG_NEEDSARG 4 + + +typedef struct LocVar { + TString *varname; + int startpc; /* first point where variable is active */ + int endpc; /* first point where variable is dead */ +} LocVar; + + + +/* +** Upvalues +*/ + +typedef struct UpVal { + CommonHeader; + TValue *v; /* points to stack or to its own value */ + union { + TValue value; /* the value (when closed) */ + struct { /* double linked list (when open) */ + struct UpVal *prev; + struct UpVal *next; + } l; + } u; +} UpVal; + + +/* +** Closures +*/ + +#define ClosureHeader \ + CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \ + struct Table *env + +typedef struct CClosure { + ClosureHeader; + lua_CFunction f; + TValue upvalue[1]; +} CClosure; + + +typedef struct LClosure { + ClosureHeader; + struct Proto *p; + UpVal *upvals[1]; +} LClosure; + + +typedef union Closure { + CClosure c; + LClosure l; +} Closure; + + +#define iscfunction(o) (ttype(o) == LUA_TFUNCTION && clvalue(o)->c.isC) +#define isLfunction(o) (ttype(o) == LUA_TFUNCTION && !clvalue(o)->c.isC) + + +/* +** Tables +*/ + +typedef union TKey { + struct { + TValuefields; + struct Node *next; /* for chaining */ + } nk; + TValue tvk; +} TKey; + + +typedef struct Node { + TValue i_val; + TKey i_key; +} Node; + + +typedef struct Table { + CommonHeader; + lu_byte flags; /* 1<<p means tagmethod(p) is not present */ + lu_byte lsizenode; /* log2 of size of `node' array */ + struct Table *metatable; + TValue *array; /* array part */ + Node *node; + Node *lastfree; /* any free position is before this position */ + GCObject *gclist; + int sizearray; /* size of `array' array */ +} Table; + + + +/* +** `module' operation for hashing (size is always a power of 2) +*/ +#define lmod(s,size) \ + (check_exp((size&(size-1))==0, (cast(int, (s) & ((size)-1))))) + + +#define twoto(x) (1<<(x)) +#define sizenode(t) (twoto((t)->lsizenode)) + + +#define luaO_nilobject (&luaO_nilobject_) + +LUAI_DATA const TValue luaO_nilobject_; + +#define ceillog2(x) (luaO_log2((x)-1) + 1) + +LUAI_FUNC int luaO_log2 (unsigned int x); +LUAI_FUNC int luaO_int2fb (unsigned int x); +LUAI_FUNC int luaO_fb2int (int x); +LUAI_FUNC int luaO_rawequalObj (const TValue *t1, const TValue *t2); +LUAI_FUNC int luaO_str2d (const char *s, lua_Number *result); +LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt, + va_list argp); +LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...); +LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t len); + + +#endif + diff --git a/engines/sword25/util/pluto/pdep/lopcodes.h b/engines/sword25/util/pluto/pdep/lopcodes.h new file mode 100644 index 0000000000..e1aed0f637 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/lopcodes.h @@ -0,0 +1,268 @@ +/* +** $Id$ +** Opcodes for Lua virtual machine +** See Copyright Notice in lua.h +*/ + +#ifndef lopcodes_h +#define lopcodes_h + +#include "llimits.h" + + +/*=========================================================================== + We assume that instructions are unsigned numbers. + All instructions have an opcode in the first 6 bits. + Instructions can have the following fields: + `A' : 8 bits + `B' : 9 bits + `C' : 9 bits + `Bx' : 18 bits (`B' and `C' together) + `sBx' : signed Bx + + A signed argument is represented in excess K; that is, the number + value is the unsigned value minus K. K is exactly the maximum value + for that argument (so that -max is represented by 0, and +max is + represented by 2*max), which is half the maximum for the corresponding + unsigned argument. +===========================================================================*/ + + +enum OpMode {iABC, iABx, iAsBx}; /* basic instruction format */ + + +/* +** size and position of opcode arguments. +*/ +#define SIZE_C 9 +#define SIZE_B 9 +#define SIZE_Bx (SIZE_C + SIZE_B) +#define SIZE_A 8 + +#define SIZE_OP 6 + +#define POS_OP 0 +#define POS_A (POS_OP + SIZE_OP) +#define POS_C (POS_A + SIZE_A) +#define POS_B (POS_C + SIZE_C) +#define POS_Bx POS_C + + +/* +** limits for opcode arguments. +** we use (signed) int to manipulate most arguments, +** so they must fit in LUAI_BITSINT-1 bits (-1 for sign) +*/ +#if SIZE_Bx < LUAI_BITSINT-1 +#define MAXARG_Bx ((1<<SIZE_Bx)-1) +#define MAXARG_sBx (MAXARG_Bx>>1) /* `sBx' is signed */ +#else +#define MAXARG_Bx MAX_INT +#define MAXARG_sBx MAX_INT +#endif + + +#define MAXARG_A ((1<<SIZE_A)-1) +#define MAXARG_B ((1<<SIZE_B)-1) +#define MAXARG_C ((1<<SIZE_C)-1) + + +/* creates a mask with `n' 1 bits at position `p' */ +#define MASK1(n,p) ((~((~(Instruction)0)<<n))<<p) + +/* creates a mask with `n' 0 bits at position `p' */ +#define MASK0(n,p) (~MASK1(n,p)) + +/* +** the following macros help to manipulate instructions +*/ + +#define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0))) +#define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ + ((cast(Instruction, o)<<POS_OP)&MASK1(SIZE_OP,POS_OP)))) + +#define GETARG_A(i) (cast(int, ((i)>>POS_A) & MASK1(SIZE_A,0))) +#define SETARG_A(i,u) ((i) = (((i)&MASK0(SIZE_A,POS_A)) | \ + ((cast(Instruction, u)<<POS_A)&MASK1(SIZE_A,POS_A)))) + +#define GETARG_B(i) (cast(int, ((i)>>POS_B) & MASK1(SIZE_B,0))) +#define SETARG_B(i,b) ((i) = (((i)&MASK0(SIZE_B,POS_B)) | \ + ((cast(Instruction, b)<<POS_B)&MASK1(SIZE_B,POS_B)))) + +#define GETARG_C(i) (cast(int, ((i)>>POS_C) & MASK1(SIZE_C,0))) +#define SETARG_C(i,b) ((i) = (((i)&MASK0(SIZE_C,POS_C)) | \ + ((cast(Instruction, b)<<POS_C)&MASK1(SIZE_C,POS_C)))) + +#define GETARG_Bx(i) (cast(int, ((i)>>POS_Bx) & MASK1(SIZE_Bx,0))) +#define SETARG_Bx(i,b) ((i) = (((i)&MASK0(SIZE_Bx,POS_Bx)) | \ + ((cast(Instruction, b)<<POS_Bx)&MASK1(SIZE_Bx,POS_Bx)))) + +#define GETARG_sBx(i) (GETARG_Bx(i)-MAXARG_sBx) +#define SETARG_sBx(i,b) SETARG_Bx((i),cast(unsigned int, (b)+MAXARG_sBx)) + + +#define CREATE_ABC(o,a,b,c) ((cast(Instruction, o)<<POS_OP) \ + | (cast(Instruction, a)<<POS_A) \ + | (cast(Instruction, b)<<POS_B) \ + | (cast(Instruction, c)<<POS_C)) + +#define CREATE_ABx(o,a,bc) ((cast(Instruction, o)<<POS_OP) \ + | (cast(Instruction, a)<<POS_A) \ + | (cast(Instruction, bc)<<POS_Bx)) + + +/* +** Macros to operate RK indices +*/ + +/* this bit 1 means constant (0 means register) */ +#define BITRK (1 << (SIZE_B - 1)) + +/* test whether value is a constant */ +#define ISK(x) ((x) & BITRK) + +/* gets the index of the constant */ +#define INDEXK(r) ((int)(r) & ~BITRK) + +#define MAXINDEXRK (BITRK - 1) + +/* code a constant index as a RK value */ +#define RKASK(x) ((x) | BITRK) + + +/* +** invalid register that fits in 8 bits +*/ +#define NO_REG MAXARG_A + + +/* +** R(x) - register +** Kst(x) - constant (in constant table) +** RK(x) == if ISK(x) then Kst(INDEXK(x)) else R(x) +*/ + + +/* +** grep "ORDER OP" if you change these enums +*/ + +typedef enum { +/*---------------------------------------------------------------------- +name args description +------------------------------------------------------------------------*/ +OP_MOVE,/* A B R(A) := R(B) */ +OP_LOADK,/* A Bx R(A) := Kst(Bx) */ +OP_LOADBOOL,/* A B C R(A) := (Bool)B; if (C) pc++ */ +OP_LOADNIL,/* A B R(A) := ... := R(B) := nil */ +OP_GETUPVAL,/* A B R(A) := UpValue[B] */ + +OP_GETGLOBAL,/* A Bx R(A) := Gbl[Kst(Bx)] */ +OP_GETTABLE,/* A B C R(A) := R(B)[RK(C)] */ + +OP_SETGLOBAL,/* A Bx Gbl[Kst(Bx)] := R(A) */ +OP_SETUPVAL,/* A B UpValue[B] := R(A) */ +OP_SETTABLE,/* A B C R(A)[RK(B)] := RK(C) */ + +OP_NEWTABLE,/* A B C R(A) := {} (size = B,C) */ + +OP_SELF,/* A B C R(A+1) := R(B); R(A) := R(B)[RK(C)] */ + +OP_ADD,/* A B C R(A) := RK(B) + RK(C) */ +OP_SUB,/* A B C R(A) := RK(B) - RK(C) */ +OP_MUL,/* A B C R(A) := RK(B) * RK(C) */ +OP_DIV,/* A B C R(A) := RK(B) / RK(C) */ +OP_MOD,/* A B C R(A) := RK(B) % RK(C) */ +OP_POW,/* A B C R(A) := RK(B) ^ RK(C) */ +OP_UNM,/* A B R(A) := -R(B) */ +OP_NOT,/* A B R(A) := not R(B) */ +OP_LEN,/* A B R(A) := length of R(B) */ + +OP_CONCAT,/* A B C R(A) := R(B).. ... ..R(C) */ + +OP_JMP,/* sBx pc+=sBx */ + +OP_EQ,/* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */ +OP_LT,/* A B C if ((RK(B) < RK(C)) ~= A) then pc++ */ +OP_LE,/* A B C if ((RK(B) <= RK(C)) ~= A) then pc++ */ + +OP_TEST,/* A C if not (R(A) <=> C) then pc++ */ +OP_TESTSET,/* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ + +OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */ +OP_TAILCALL,/* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ +OP_RETURN,/* A B return R(A), ... ,R(A+B-2) (see note) */ + +OP_FORLOOP,/* A sBx R(A)+=R(A+2); + if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/ +OP_FORPREP,/* A sBx R(A)-=R(A+2); pc+=sBx */ + +OP_TFORLOOP,/* A C R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2)); + if R(A+3) ~= nil then R(A+2)=R(A+3) else pc++ */ +OP_SETLIST,/* A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B */ + +OP_CLOSE,/* A close all variables in the stack up to (>=) R(A)*/ +OP_CLOSURE,/* A Bx R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n)) */ + +OP_VARARG/* A B R(A), R(A+1), ..., R(A+B-1) = vararg */ +} OpCode; + + +#define NUM_OPCODES (cast(int, OP_VARARG) + 1) + + + +/*=========================================================================== + Notes: + (*) In OP_CALL, if (B == 0) then B = top. C is the number of returns - 1, + and can be 0: OP_CALL then sets `top' to last_result+1, so + next open instruction (OP_CALL, OP_RETURN, OP_SETLIST) may use `top'. + + (*) In OP_VARARG, if (B == 0) then use actual number of varargs and + set top (like in OP_CALL with C == 0). + + (*) In OP_RETURN, if (B == 0) then return up to `top' + + (*) In OP_SETLIST, if (B == 0) then B = `top'; + if (C == 0) then next `instruction' is real C + + (*) For comparisons, A specifies what condition the test should accept + (true or false). + + (*) All `skips' (pc++) assume that next instruction is a jump +===========================================================================*/ + + +/* +** masks for instruction properties. The format is: +** bits 0-1: op mode +** bits 2-3: C arg mode +** bits 4-5: B arg mode +** bit 6: instruction set register A +** bit 7: operator is a test +*/ + +enum OpArgMask { + OpArgN, /* argument is not used */ + OpArgU, /* argument is used */ + OpArgR, /* argument is a register or a jump offset */ + OpArgK /* argument is a constant or register/constant */ +}; + +LUAI_DATA const lu_byte luaP_opmodes[NUM_OPCODES]; + +#define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 3)) +#define getBMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 4) & 3)) +#define getCMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 2) & 3)) +#define testAMode(m) (luaP_opmodes[m] & (1 << 6)) +#define testTMode(m) (luaP_opmodes[m] & (1 << 7)) + + +LUAI_DATA const char *const luaP_opnames[NUM_OPCODES+1]; /* opcode names */ + + +/* number of list items to accumulate before a SETLIST instruction */ +#define LFIELDS_PER_FLUSH 50 + + +#endif diff --git a/engines/sword25/util/pluto/pdep/lstate.h b/engines/sword25/util/pluto/pdep/lstate.h new file mode 100644 index 0000000000..94a6249461 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/lstate.h @@ -0,0 +1,169 @@ +/* +** $Id$ +** Global State +** See Copyright Notice in lua.h +*/ + +#ifndef lstate_h +#define lstate_h + +#include "lua.h" + +#include "lobject.h" +#include "ltm.h" +#include "lzio.h" + + + +struct lua_longjmp; /* defined in ldo.c */ + + +/* table of globals */ +#define gt(L) (&L->l_gt) + +/* registry */ +#define registry(L) (&G(L)->l_registry) + + +/* extra stack space to handle TM calls and some other extras */ +#define EXTRA_STACK 5 + + +#define BASIC_CI_SIZE 8 + +#define BASIC_STACK_SIZE (2*LUA_MINSTACK) + + + +typedef struct stringtable { + GCObject **hash; + lu_int32 nuse; /* number of elements */ + int size; +} stringtable; + + +/* +** informations about a call +*/ +typedef struct CallInfo { + StkId base; /* base for this function */ + StkId func; /* function index in the stack */ + StkId top; /* top for this function */ + const Instruction *savedpc; + int nresults; /* expected number of results from this function */ + int tailcalls; /* number of tail calls lost under this entry */ +} CallInfo; + + + +#define curr_func(L) (clvalue(L->ci->func)) +#define ci_func(ci) (clvalue((ci)->func)) +#define f_isLua(ci) (!ci_func(ci)->c.isC) +#define isLua(ci) (ttisfunction((ci)->func) && f_isLua(ci)) + + +/* +** `global state', shared by all threads of this state +*/ +typedef struct global_State { + stringtable strt; /* hash table for strings */ + lua_Alloc frealloc; /* function to reallocate memory */ + void *ud; /* auxiliary data to `frealloc' */ + lu_byte currentwhite; + lu_byte gcstate; /* state of garbage collector */ + int sweepstrgc; /* position of sweep in `strt' */ + GCObject *rootgc; /* list of all collectable objects */ + GCObject **sweepgc; /* position of sweep in `rootgc' */ + GCObject *gray; /* list of gray objects */ + GCObject *grayagain; /* list of objects to be traversed atomically */ + GCObject *weak; /* list of weak tables (to be cleared) */ + GCObject *tmudata; /* last element of list of userdata to be GC */ + Mbuffer buff; /* temporary buffer for string concatentation */ + lu_mem GCthreshold; + lu_mem totalbytes; /* number of bytes currently allocated */ + lu_mem estimate; /* an estimate of number of bytes actually in use */ + lu_mem gcdept; /* how much GC is `behind schedule' */ + int gcpause; /* size of pause between successive GCs */ + int gcstepmul; /* GC `granularity' */ + lua_CFunction panic; /* to be called in unprotected errors */ + TValue l_registry; + struct lua_State *mainthread; + UpVal uvhead; /* head of double-linked list of all open upvalues */ + struct Table *mt[NUM_TAGS]; /* metatables for basic types */ + TString *tmname[TM_N]; /* array with tag-method names */ +} global_State; + + +/* +** `per thread' state +*/ +struct lua_State { + CommonHeader; + lu_byte status; + StkId top; /* first free slot in the stack */ + StkId base; /* base of current function */ + global_State *l_G; + CallInfo *ci; /* call info for current function */ + const Instruction *savedpc; /* `savedpc' of current function */ + StkId stack_last; /* last free slot in the stack */ + StkId stack; /* stack base */ + CallInfo *end_ci; /* points after end of ci array*/ + CallInfo *base_ci; /* array of CallInfo's */ + int stacksize; + int size_ci; /* size of array `base_ci' */ + unsigned short nCcalls; /* number of nested C calls */ + unsigned short baseCcalls; /* nested C calls when resuming coroutine */ + lu_byte hookmask; + lu_byte allowhook; + int basehookcount; + int hookcount; + lua_Hook hook; + TValue l_gt; /* table of globals */ + TValue env; /* temporary place for environments */ + GCObject *openupval; /* list of open upvalues in this stack */ + GCObject *gclist; + struct lua_longjmp *errorJmp; /* current error recover point */ + ptrdiff_t errfunc; /* current error handling function (stack index) */ +}; + + +#define G(L) (L->l_G) + + +/* +** Union of all collectable objects +*/ +union GCObject { + GCheader gch; + union TString ts; + union Udata u; + union Closure cl; + struct Table h; + struct Proto p; + struct UpVal uv; + struct lua_State th; /* thread */ +}; + + +/* macros to convert a GCObject into a specific value */ +#define rawgco2ts(o) check_exp((o)->gch.tt == LUA_TSTRING, &((o)->ts)) +#define gco2ts(o) (&rawgco2ts(o)->tsv) +#define rawgco2u(o) check_exp((o)->gch.tt == LUA_TUSERDATA, &((o)->u)) +#define gco2u(o) (&rawgco2u(o)->uv) +#define gco2cl(o) check_exp((o)->gch.tt == LUA_TFUNCTION, &((o)->cl)) +#define gco2h(o) check_exp((o)->gch.tt == LUA_TTABLE, &((o)->h)) +#define gco2p(o) check_exp((o)->gch.tt == LUA_TPROTO, &((o)->p)) +#define gco2uv(o) check_exp((o)->gch.tt == LUA_TUPVAL, &((o)->uv)) +#define ngcotouv(o) \ + check_exp((o) == NULL || (o)->gch.tt == LUA_TUPVAL, &((o)->uv)) +#define gco2th(o) check_exp((o)->gch.tt == LUA_TTHREAD, &((o)->th)) + +/* macro to convert any Lua object into a GCObject */ +#define obj2gco(v) (cast(GCObject *, (v))) + + +LUAI_FUNC lua_State *luaE_newthread (lua_State *L); +LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); + +#endif + diff --git a/engines/sword25/util/pluto/pdep/lstring.h b/engines/sword25/util/pluto/pdep/lstring.h new file mode 100644 index 0000000000..c88e4c12a9 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/lstring.h @@ -0,0 +1,31 @@ +/* +** $Id$ +** String table (keep all strings handled by Lua) +** See Copyright Notice in lua.h +*/ + +#ifndef lstring_h +#define lstring_h + + +#include "lgc.h" +#include "lobject.h" +#include "lstate.h" + + +#define sizestring(s) (sizeof(union TString)+((s)->len+1)*sizeof(char)) + +#define sizeudata(u) (sizeof(union Udata)+(u)->len) + +#define luaS_new(L, s) (luaS_newlstr(L, s, strlen(s))) +#define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ + (sizeof(s)/sizeof(char))-1)) + +#define luaS_fix(s) l_setbit((s)->tsv.marked, FIXEDBIT) + +LUAI_FUNC void luaS_resize (lua_State *L, int newsize); +LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, Table *e); +LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); + + +#endif diff --git a/engines/sword25/util/pluto/pdep/ltm.h b/engines/sword25/util/pluto/pdep/ltm.h new file mode 100644 index 0000000000..1b89683ef3 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/ltm.h @@ -0,0 +1,54 @@ +/* +** $Id$ +** Tag methods +** See Copyright Notice in lua.h +*/ + +#ifndef ltm_h +#define ltm_h + + +#include "lobject.h" + + +/* +* WARNING: if you change the order of this enumeration, +* grep "ORDER TM" +*/ +typedef enum { + TM_INDEX, + TM_NEWINDEX, + TM_GC, + TM_MODE, + TM_EQ, /* last tag method with `fast' access */ + TM_ADD, + TM_SUB, + TM_MUL, + TM_DIV, + TM_MOD, + TM_POW, + TM_UNM, + TM_LEN, + TM_LT, + TM_LE, + TM_CONCAT, + TM_CALL, + TM_N /* number of elements in the enum */ +} TMS; + + + +#define gfasttm(g,et,e) ((et) == NULL ? NULL : \ + ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e])) + +#define fasttm(l,et,e) gfasttm(G(l), et, e) + +LUAI_DATA const char *const luaT_typenames[]; + + +LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); +LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, + TMS event); +LUAI_FUNC void luaT_init (lua_State *L); + +#endif diff --git a/engines/sword25/util/pluto/pdep/lua.h b/engines/sword25/util/pluto/pdep/lua.h new file mode 100644 index 0000000000..68dd887f0f --- /dev/null +++ b/engines/sword25/util/pluto/pdep/lua.h @@ -0,0 +1,388 @@ +/* +** $Id$ +** Lua - An Extensible Extension Language +** Lua.org, PUC-Rio, Brazil (http://www.lua.org) +** See Copyright Notice at the end of this file +*/ + + +#ifndef lua_h +#define lua_h + +#include <stdarg.h> +#include <stddef.h> + + +#include "sword25/util/lua/luaconf.h" + + +#define LUA_VERSION "Lua 5.1" +#define LUA_RELEASE "Lua 5.1.3" +#define LUA_VERSION_NUM 501 +#define LUA_COPYRIGHT "Copyright (C) 1994-2008 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes" + + +/* mark for precompiled code (`<esc>Lua') */ +#define LUA_SIGNATURE "\033Lua" + +/* option for multiple returns in `lua_pcall' and `lua_call' */ +#define LUA_MULTRET (-1) + + +/* +** pseudo-indices +*/ +#define LUA_REGISTRYINDEX (-10000) +#define LUA_ENVIRONINDEX (-10001) +#define LUA_GLOBALSINDEX (-10002) +#define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i)) + + +/* thread status; 0 is OK */ +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRERR 5 + + +typedef struct lua_State lua_State; + +typedef int (*lua_CFunction) (lua_State *L); + + +/* +** functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud); + + +/* +** prototype for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + + +/* +** basic types +*/ +#define LUA_TNONE (-1) + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + + + +/* minimum Lua stack available to a C function */ +#define LUA_MINSTACK 20 + + +/* +** generic extra include file +*/ +#if defined(LUA_USER_H) +#include LUA_USER_H +#endif + + +/* type of numbers in Lua */ +typedef LUA_NUMBER lua_Number; + + +/* type for integer functions */ +typedef LUA_INTEGER lua_Integer; + + + +/* +** state manipulation +*/ +LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); +LUA_API void (lua_close) (lua_State *L); +LUA_API lua_State *(lua_newthread) (lua_State *L); + +LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); + + +/* +** basic stack manipulation +*/ +LUA_API int (lua_gettop) (lua_State *L); +LUA_API void (lua_settop) (lua_State *L, int idx); +LUA_API void (lua_pushvalue) (lua_State *L, int idx); +LUA_API void (lua_remove) (lua_State *L, int idx); +LUA_API void (lua_insert) (lua_State *L, int idx); +LUA_API void (lua_replace) (lua_State *L, int idx); +LUA_API int (lua_checkstack) (lua_State *L, int sz); + +LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); + + +/* +** access functions (stack -> C) +*/ + +LUA_API int (lua_isnumber) (lua_State *L, int idx); +LUA_API int (lua_isstring) (lua_State *L, int idx); +LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isuserdata) (lua_State *L, int idx); +LUA_API int (lua_type) (lua_State *L, int idx); +LUA_API const char *(lua_typename) (lua_State *L, int tp); + +LUA_API int (lua_equal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_lessthan) (lua_State *L, int idx1, int idx2); + +LUA_API lua_Number (lua_tonumber) (lua_State *L, int idx); +LUA_API lua_Integer (lua_tointeger) (lua_State *L, int idx); +LUA_API int (lua_toboolean) (lua_State *L, int idx); +LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); +LUA_API size_t (lua_objlen) (lua_State *L, int idx); +LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); +LUA_API void *(lua_touserdata) (lua_State *L, int idx); +LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); +LUA_API const void *(lua_topointer) (lua_State *L, int idx); + + +/* +** push functions (C -> stack) +*/ +LUA_API void (lua_pushnil) (lua_State *L); +LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); +LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); +LUA_API void (lua_pushlstring) (lua_State *L, const char *s, size_t l); +LUA_API void (lua_pushstring) (lua_State *L, const char *s); +LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, + va_list argp); +LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); +LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); +LUA_API void (lua_pushboolean) (lua_State *L, int b); +LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); +LUA_API int (lua_pushthread) (lua_State *L); + + +/* +** get functions (Lua -> stack) +*/ +LUA_API void (lua_gettable) (lua_State *L, int idx); +LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_rawget) (lua_State *L, int idx); +LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n); +LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); +LUA_API int (lua_getmetatable) (lua_State *L, int objindex); +LUA_API void (lua_getfenv) (lua_State *L, int idx); + + +/* +** set functions (stack -> Lua) +*/ +LUA_API void (lua_settable) (lua_State *L, int idx); +LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_rawset) (lua_State *L, int idx); +LUA_API void (lua_rawseti) (lua_State *L, int idx, int n); +LUA_API int (lua_setmetatable) (lua_State *L, int objindex); +LUA_API int (lua_setfenv) (lua_State *L, int idx); + + +/* +** `load' and `call' functions (load and run Lua code) +*/ +LUA_API void (lua_call) (lua_State *L, int nargs, int nresults); +LUA_API int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc); +LUA_API int (lua_cpcall) (lua_State *L, lua_CFunction func, void *ud); +LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, + const char *chunkname); + +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data); + + +/* +** coroutine functions +*/ +LUA_API int (lua_yield) (lua_State *L, int nresults); +LUA_API int (lua_resume) (lua_State *L, int narg); +LUA_API int (lua_status) (lua_State *L); + +/* +** garbage-collection function and options +*/ + +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 + +LUA_API int (lua_gc) (lua_State *L, int what, int data); + + +/* +** miscellaneous functions +*/ + +LUA_API int (lua_error) (lua_State *L); + +LUA_API int (lua_next) (lua_State *L, int idx); + +LUA_API void (lua_concat) (lua_State *L, int n); + +LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); +LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); + + + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + +#define lua_pop(L,n) lua_settop(L, -(n)-1) + +#define lua_newtable(L) lua_createtable(L, 0, 0) + +#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) + +#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) + +#define lua_strlen(L,i) lua_objlen(L, (i)) + +#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) + +#define lua_pushliteral(L, s) \ + lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1) + +#define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s)) +#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s)) + +#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) + + + +/* +** compatibility macros and functions +*/ + +#define lua_open() luaL_newstate() + +#define lua_getregistry(L) lua_pushvalue(L, LUA_REGISTRYINDEX) + +#define lua_getgccount(L) lua_gc(L, LUA_GCCOUNT, 0) + +#define lua_Chunkreader lua_Reader +#define lua_Chunkwriter lua_Writer + + +/* hack */ +LUA_API void lua_setlevel (lua_State *from, lua_State *to); + + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + + +/* +** Event codes +*/ +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 +#define LUA_HOOKTAILRET 4 + + +/* +** Event masks +*/ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) + +typedef struct lua_Debug lua_Debug; /* activation record */ + + +/* Functions to be called by the debuger in specific events */ +typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); + + +LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar); +LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n); +LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n); + +LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook lua_gethook (lua_State *L); +LUA_API int lua_gethookmask (lua_State *L); +LUA_API int lua_gethookcount (lua_State *L); + + +struct lua_Debug { + int event; + const char *name; /* (n) */ + const char *namewhat; /* (n) `global', `local', `field', `method' */ + const char *what; /* (S) `Lua', `C', `main', `tail' */ + const char *source; /* (S) */ + int currentline; /* (l) */ + int nups; /* (u) number of upvalues */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + int i_ci; /* active function */ +}; + +/* }====================================================================== */ + + +/****************************************************************************** +* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved. +* +* 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. +******************************************************************************/ + + +#endif diff --git a/engines/sword25/util/pluto/pdep/lzio.h b/engines/sword25/util/pluto/pdep/lzio.h new file mode 100644 index 0000000000..2f167d7d58 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/lzio.h @@ -0,0 +1,65 @@ +/* +** $Id$ +** Buffered streams +** See Copyright Notice in lua.h +*/ + + +#ifndef lzio_h +#define lzio_h + +#include "lua.h" + + +#define EOZ (-1) /* end of stream */ + +typedef struct Zio ZIO; + +#define char2int(c) cast(int, cast(unsigned char, (c))) + +#define zgetc(z) (((z)->n--)>0 ? char2int(*(z)->p++) : pdep_fill(z)) + +typedef struct Mbuffer { + char *buffer; + size_t n; + size_t buffsize; +} Mbuffer; + +#define pdep_initbuffer(L, buff) ((buff)->buffer = NULL, (buff)->buffsize = 0) + +#define pdep_buffer(buff) ((buff)->buffer) +#define pdep_sizebuffer(buff) ((buff)->buffsize) +#define pdep_bufflen(buff) ((buff)->n) + +#define pdep_resetbuffer(buff) ((buff)->n = 0) + + +#define pdep_resizebuffer(L, buff, size) \ + (pdep_reallocvector(L, (buff)->buffer, (buff)->buffsize, size, char), \ + (buff)->buffsize = size) + +#define pdep_freebuffer(L, buff) pdep_resizebuffer(L, buff, 0) + + +LUAI_FUNC char *pdep_openspace (lua_State *L, Mbuffer *buff, size_t n); +LUAI_FUNC void pdep_init (lua_State *L, ZIO *z, lua_Reader reader, + void *data); +LUAI_FUNC size_t pdep_read (ZIO* z, void* b, size_t n); /* read next n bytes */ +LUAI_FUNC int pdep_lookahead (ZIO *z); + + + +/* --------- Private Part ------------------ */ + +struct Zio { + size_t n; /* bytes still unread */ + const char *p; /* current position in buffer */ + lua_Reader reader; + void* data; /* additional data */ + lua_State *L; /* Lua state (for reader) */ +}; + + +LUAI_FUNC int pdep_fill (ZIO *z); + +#endif diff --git a/engines/sword25/util/pluto/pdep/pdep.h b/engines/sword25/util/pluto/pdep/pdep.h new file mode 100644 index 0000000000..c26f4566c5 --- /dev/null +++ b/engines/sword25/util/pluto/pdep/pdep.h @@ -0,0 +1,41 @@ +#ifndef PDEP_H +#define PDEP_H + +#include "lua.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "llimits.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "lauxlib.h" + + +#define pdep_reallocv(L,b,on,n,e) \ + pdep_realloc_(L, (b), (on)*(e), (n)*(e)) +#define pdep_reallocvector(L, v,oldn,n,t) \ + ((v)=cast(t *, pdep_reallocv(L, v, oldn, n, sizeof(t)))) +#define pdep_freearray(L, b, n, t) pdep_reallocv(L, (b), n, 0, sizeof(t)) +#define pdep_newvector(L,n,t) \ + cast(t *, pdep_reallocv(L, NULL, 0, n, sizeof(t))) +#define pdep_new(L,t) cast(t *, pdep_malloc(L, sizeof(t))) +#define pdep_malloc(L,t) pdep_realloc_(L, NULL, 0, (t)) +#define pdep_checkstack(L,n) \ + if ((char *)L->stack_last - (char *)L->top <= (n)*(int)sizeof(TValue)) \ + pdep_growstack(L, n); \ + else pdep_reallocstack(L, L->stacksize - EXTRA_STACK - 1); + + +void pdep_pushobject (lua_State *L, const TValue *o); +void *pdep_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize); +void pdep_link (lua_State *L, GCObject *o, lu_byte tt); +Proto *pdep_newproto (lua_State *L); +Closure *pdep_newLclosure (lua_State *L, int nelems, Table *e); +void pdep_reallocstack (lua_State *L, int newsize); +void pdep_growstack (lua_State *L, int n); +void pdep_reallocCI (lua_State *L, int newsize); +TString *pdep_newlstr (lua_State *L, const char *str, size_t l); + +#endif diff --git a/engines/sword25/util/pluto/pluto.cpp b/engines/sword25/util/pluto/pluto.cpp new file mode 100644 index 0000000000..957f5af795 --- /dev/null +++ b/engines/sword25/util/pluto/pluto.cpp @@ -0,0 +1,1665 @@ +/* $Id$ */ + +/* Pluto - Heavy-duty persistence for Lua + * Copyright (C) 2004 by Ben Sunshine-Hill, and released into the public + * domain. People making use of this software as part of an application + * are politely requested to email the author at sneftel@gmail.com + * with a brief description of the application, primarily to satisfy his + * curiosity. + * + * 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 "sword25/util/lua/lua.h" +#include "pluto.h" + +#define USE_PDEP + +#ifdef USE_PDEP +#include "pdep/pdep.h" +#define LIF(prefix, name) pdep ## _ ## name +#else +#include "lapi.h" +#include "ldo.h" +#include "lfunc.h" +#include "lgc.h" +#include "llimits.h" +#include "lmem.h" +#include "lobject.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "lauxlib.h" +#define LIF(prefix, name) lua ## prefix ## _ ## name +#endif + +#include <string.h> + + + +/* #define PLUTO_DEBUG */ + + + + +#ifdef PLUTO_DEBUG +#include <stdio.h> +#endif + +#define PLUTO_TPERMANENT 101 + +#define verify(x) { int v = (int)((x)); v=v; lua_assert(v); } + +typedef struct PersistInfo_t { + lua_State *L; + int counter; + lua_Chunkwriter writer; + void *ud; +#ifdef PLUTO_DEBUG + int level; +#endif +} PersistInfo; + +#ifdef PLUTO_DEBUG +void printindent(int indent) +{ + int il; + for(il=0; il<indent; il++) { + printf(" "); + } +} +#endif + +/* Mutual recursion requires prototype */ +static void persist(PersistInfo *pi); + +/* A simple reimplementation of the unfortunately static function luaA_index. + * Does not support the global table, registry, or upvalues. */ +static StkId getobject(lua_State *L, int stackpos) +{ + if(stackpos > 0) { + lua_assert(L->base+stackpos-1 < L->top); + return L->base+stackpos-1; + } else { + lua_assert(L->top-stackpos >= L->base); + return L->top+stackpos; + } +} + +/* Choose whether to do a regular or special persistence based on an object's + * metatable. "default" is whether the object, if it doesn't have a __persist + * entry, is literally persistable or not. + * Pushes the unpersist closure and returns true if special persistence is + * used. */ +static int persistspecialobject(PersistInfo *pi, int defaction) +{ + /* perms reftbl ... obj */ + lua_checkstack(pi->L, 4); + /* Check whether we should persist literally, or via the __persist + * metafunction */ + if(!lua_getmetatable(pi->L, -1)) { + if(defaction) { + { + int zero = 0; + pi->writer(pi->L, &zero, sizeof(int), pi->ud); + } + return 0; + } else { + lua_pushstring(pi->L, "Type not literally persistable by default"); + lua_error(pi->L); + } + } + /* perms reftbl sptbl ... obj mt */ + lua_pushstring(pi->L, "__persist"); + /* perms reftbl sptbl ... obj mt "__persist" */ + lua_rawget(pi->L, -2); + /* perms reftbl sptbl ... obj mt __persist? */ + if(lua_isnil(pi->L, -1)) { + /* perms reftbl sptbl ... obj mt nil */ + lua_pop(pi->L, 2); + /* perms reftbl sptbl ... obj */ + if(defaction) { + { + int zero = 0; + pi->writer(pi->L, &zero, sizeof(int), pi->ud); + } + return 0; + } else { + lua_pushstring(pi->L, "Type not literally persistable by default"); + lua_error(pi->L); + return 0; /* not reached */ + } + } else if(lua_isboolean(pi->L, -1)) { + /* perms reftbl sptbl ... obj mt bool */ + if(lua_toboolean(pi->L, -1)) { + /* perms reftbl sptbl ... obj mt true */ + lua_pop(pi->L, 2); + /* perms reftbl sptbl ... obj */ + { + int zero = 0; + pi->writer(pi->L, &zero, sizeof(int), pi->ud); + } + return 0; + } else { + lua_pushstring(pi->L, "Metatable forbade persistence"); + lua_error(pi->L); + return 0; /* not reached */ + } + } else if(!lua_isfunction(pi->L, -1)) { + lua_pushstring(pi->L, "__persist not nil, boolean, or function"); + lua_error(pi->L); + } + /* perms reftbl ... obj mt __persist */ + lua_pushvalue(pi->L, -3); + /* perms reftbl ... obj mt __persist obj */ +#ifdef PLUTO_PASS_USERDATA_TO_PERSIST + lua_pushlightuserdata(pi->L, (void*)pi->writer); + lua_pushlightuserdata(pi->L, pi->ud); + /* perms reftbl ... obj mt __persist obj ud */ + lua_call(pi->L, 3, 1); + /* perms reftbl ... obj mt func? */ +#else + lua_call(pi->L, 1, 1); + /* perms reftbl ... obj mt func? */ +#endif + /* perms reftbl ... obj mt func? */ + if(!lua_isfunction(pi->L, -1)) { + lua_pushstring(pi->L, "__persist function did not return a function"); + lua_error(pi->L); + } + /* perms reftbl ... obj mt func */ + { + int one = 1; + pi->writer(pi->L, &one, sizeof(int), pi->ud); + } + persist(pi); + /* perms reftbl ... obj mt func */ + lua_pop(pi->L, 2); + /* perms reftbl ... obj */ + return 1; +} + +static void persisttable(PersistInfo *pi) +{ + /* perms reftbl ... tbl */ + lua_checkstack(pi->L, 3); + if(persistspecialobject(pi, 1)) { + /* perms reftbl ... tbl */ + return; + } + /* perms reftbl ... tbl */ + /* First, persist the metatable (if any) */ + if(!lua_getmetatable(pi->L, -1)) { + lua_pushnil(pi->L); + } + /* perms reftbl ... tbl mt/nil */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... tbl */ + + /* Now, persist all k/v pairs */ + lua_pushnil(pi->L); + /* perms reftbl ... tbl nil */ + while(lua_next(pi->L, -2)) { + /* perms reftbl ... tbl k v */ + lua_pushvalue(pi->L, -2); + /* perms reftbl ... tbl k v k */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... tbl k v */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... tbl k */ + } + /* perms reftbl ... tbl */ + /* Terminate list */ + lua_pushnil(pi->L); + /* perms reftbl ... tbl nil */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... tbl */ +} + +static void persistuserdata(PersistInfo *pi) { + /* perms reftbl ... udata */ + lua_checkstack(pi->L, 2); + if(persistspecialobject(pi, 0)) { + /* perms reftbl ... udata */ + return; + } else { + /* Use literal persistence */ + size_t length = uvalue(getobject(pi->L, -1))->len; + pi->writer(pi->L, &length, sizeof(size_t), pi->ud); + pi->writer(pi->L, lua_touserdata(pi->L, -1), length, pi->ud); + if(!lua_getmetatable(pi->L, -1)) { + /* perms reftbl ... udata */ + lua_pushnil(pi->L); + /* perms reftbl ... udata mt/nil */ + } + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... udata */ + } +} + + +static Proto *toproto(lua_State *L, int stackpos) +{ + return gco2p(getobject(L, stackpos)->value.gc); +} + +static UpVal *toupval(lua_State *L, int stackpos) +{ + lua_assert(ttype(getobject(L, stackpos)) == LUA_TUPVAL); + return gco2uv(getobject(L, stackpos)->value.gc); +} + +static void pushproto(lua_State *L, Proto *proto) +{ + TValue o; + setptvalue(L, &o, proto); + LIF(A,pushobject)(L, &o); +} + +#define setuvvalue(L,obj,x) \ + { TValue *i_o=(obj); \ + i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TUPVAL; \ + checkliveness(G(L),i_o); } + +static void pushupval(lua_State *L, UpVal *upval) +{ + TValue o; + setuvvalue(L, &o, upval); + LIF(A,pushobject)(L, &o); +} + +static void pushclosure(lua_State *L, Closure *closure) +{ + TValue o; + setclvalue(L, &o, closure); + LIF(A,pushobject)(L, &o); +} + +static void pushstring(lua_State *L, TString *s) +{ + TValue o; + setsvalue(L, &o, s); + LIF(A,pushobject)(L, &o); +} + +static void persistfunction(PersistInfo *pi) +{ + /* perms reftbl ... func */ + Closure *cl = clvalue(getobject(pi->L, -1)); + lua_checkstack(pi->L, 2); + if(cl->c.isC) { + /* It's a C function. For now, we aren't going to allow + * persistence of C closures, even if the "C proto" is + * already in the permanents table. */ + lua_pushstring(pi->L, "Attempt to persist a C function"); + lua_error(pi->L); + } else { + /* It's a Lua closure. */ + { + /* We don't really _NEED_ the number of upvals, + * but it'll simplify things a bit */ + pi->writer(pi->L, &cl->l.p->nups, sizeof(lu_byte), pi->ud); + } + /* Persist prototype */ + { + pushproto(pi->L, cl->l.p); + /* perms reftbl ... func proto */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... func */ + } + /* Persist upvalue values (not the upvalue objects + * themselves) */ + { + int i; + for(i=0; i<cl->l.p->nups; i++) { + /* perms reftbl ... func */ + pushupval(pi->L, cl->l.upvals[i]); + /* perms reftbl ... func upval */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... func */ + } + /* perms reftbl ... func */ + } + /* Persist function environment */ + { + lua_getfenv(pi->L, -1); + /* perms reftbl ... func fenv */ + if(lua_equal(pi->L, -1, LUA_GLOBALSINDEX)) { + /* Function has the default fenv */ + /* perms reftbl ... func _G */ + lua_pop(pi->L, 1); + /* perms reftbl ... func */ + lua_pushnil(pi->L); + /* perms reftbl ... func nil */ + } + /* perms reftbl ... func fenv/nil */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... func */ + } + } +} + + +/* Upvalues are tricky. Here's why. + * + * A particular upvalue may be either "open", in which case its member v + * points into a thread's stack, or "closed" in which case it points to the + * upvalue itself. An upvalue is closed under any of the following conditions: + * -- The function that initially declared the variable "local" returns + * -- The thread in which the closure was created is garbage collected + * + * To make things wackier, just because a thread is reachable by Lua doesn't + * mean it's in our root set. We need to be able to treat an open upvalue + * from an unreachable thread as a closed upvalue. + * + * The solution: + * (a) For the purposes of persisting, don't indicate whether an upvalue is + * closed or not. + * (b) When unpersisting, pretend that all upvalues are closed. + * (c) When persisting, persist all open upvalues referenced by a thread + * that is persisted, and tag each one with the corresponding stack position + * (d) When unpersisting, "reopen" each of these upvalues as the thread is + * unpersisted + */ +static void persistupval(PersistInfo *pi) +{ + /* perms reftbl ... upval */ + UpVal *uv = toupval(pi->L, -1); + lua_checkstack(pi->L, 1); + + /* We can't permit the upval to linger around on the stack, as Lua + * will bail if its GC finds it. */ + + lua_pop(pi->L, 1); + /* perms reftbl ... */ + LIF(A,pushobject)(pi->L, uv->v); + /* perms reftbl ... obj */ + persist(pi); + /* perms reftbl ... obj */ +} + +static void persistproto(PersistInfo *pi) +{ + /* perms reftbl ... proto */ + Proto *p = toproto(pi->L, -1); + lua_checkstack(pi->L, 2); + + /* Persist constant refs */ + { + int i; + pi->writer(pi->L, &p->sizek, sizeof(int), pi->ud); + for(i=0; i<p->sizek; i++) { + LIF(A,pushobject)(pi->L, &p->k[i]); + /* perms reftbl ... proto const */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... proto */ + } + } + /* perms reftbl ... proto */ + + /* serialize inner Proto refs */ + { + int i; + pi->writer(pi->L, &p->sizep, sizeof(int), pi->ud); + for(i=0; i<p->sizep; i++) + { + pushproto(pi->L, p->p[i]); + /* perms reftbl ... proto subproto */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... proto */ + } + } + /* perms reftbl ... proto */ + + /* Serialize code */ + { + pi->writer(pi->L, &p->sizecode, sizeof(int), pi->ud); + pi->writer(pi->L, p->code, sizeof(Instruction) * p->sizecode, pi->ud); + } + + /* Serialize upvalue names */ + { + int i; + pi->writer(pi->L, &p->sizeupvalues, sizeof(int), pi->ud); + for(i=0; i<p->sizeupvalues; i++) + { + pushstring(pi->L, p->upvalues[i]); + persist(pi); + lua_pop(pi->L, 1); + } + } + /* Serialize local variable infos */ + { + int i; + pi->writer(pi->L, &p->sizelocvars, sizeof(int), pi->ud); + for(i=0; i<p->sizelocvars; i++) + { + pushstring(pi->L, p->locvars[i].varname); + persist(pi); + lua_pop(pi->L, 1); + + pi->writer(pi->L, &p->locvars[i].startpc, sizeof(int), pi->ud); + pi->writer(pi->L, &p->locvars[i].endpc, sizeof(int), pi->ud); + } + } + + /* Serialize source string */ + pushstring(pi->L, p->source); + persist(pi); + lua_pop(pi->L, 1); + + /* Serialize line numbers */ + { + pi->writer(pi->L, &p->sizelineinfo, sizeof(int), pi->ud); + if (p->sizelineinfo) + { + pi->writer(pi->L, p->lineinfo, sizeof(int) * p->sizelineinfo, pi->ud); + } + } + + /* Serialize linedefined and lastlinedefined */ + pi->writer(pi->L, &p->linedefined, sizeof(int), pi->ud); + pi->writer(pi->L, &p->lastlinedefined, sizeof(int), pi->ud); + + /* Serialize misc values */ + { + pi->writer(pi->L, &p->nups, sizeof(lu_byte), pi->ud); + pi->writer(pi->L, &p->numparams, sizeof(lu_byte), pi->ud); + pi->writer(pi->L, &p->is_vararg, sizeof(lu_byte), pi->ud); + pi->writer(pi->L, &p->maxstacksize, sizeof(lu_byte), pi->ud); + } + /* We do not currently persist upvalue names, local variable names, + * variable lifetimes, line info, or source code. */ +} + +/* Copies a stack, but the stack is reversed in the process + */ +static size_t revappendstack(lua_State *from, lua_State *to) +{ + StkId o; + for(o=from->top-1; o>=from->stack; o--) { + setobj2s(to, to->top, o); + to->top++; + } + return from->top - from->stack; +} + +/* Persist all stack members + */ +static void persistthread(PersistInfo *pi) +{ + size_t posremaining; + lua_State *L2; + /* perms reftbl ... thr */ + L2 = lua_tothread(pi->L, -1); + lua_checkstack(pi->L, L2->top - L2->stack + 1); + if(pi->L == L2) { + lua_pushstring(pi->L, "Can't persist currently running thread"); + lua_error(pi->L); + return; /* not reached */ + } + + /* Persist the stack */ + posremaining = revappendstack(L2, pi->L); + /* perms reftbl ... thr (rev'ed contents of L2) */ + pi->writer(pi->L, &posremaining, sizeof(size_t), pi->ud); + for(; posremaining > 0; posremaining--) { + persist(pi); + lua_pop(pi->L, 1); + } + /* perms reftbl ... thr */ + /* Now, persist the CallInfo stack. */ + { + size_t i, numframes = (L2->ci - L2->base_ci) + 1; + pi->writer(pi->L, &numframes, sizeof(size_t), pi->ud); + for(i=0; i<numframes; i++) { + CallInfo *ci = L2->base_ci + i; + size_t stackbase = ci->base - L2->stack; + size_t stackfunc = ci->func - L2->stack; + size_t stacktop = ci->top - L2->stack; + size_t savedpc = (ci != L2->base_ci) ? + ci->savedpc - ci_func(ci)->l.p->code : + 0; + pi->writer(pi->L, &stackbase, sizeof(size_t), pi->ud); + pi->writer(pi->L, &stackfunc, sizeof(size_t), pi->ud); + pi->writer(pi->L, &stacktop, sizeof(size_t), pi->ud); + pi->writer(pi->L, &ci->nresults, sizeof(int), pi->ud); + pi->writer(pi->L, &savedpc, sizeof(size_t), pi->ud); + } + } + + /* Serialize the state's other parameters, with the exception of upval stuff */ + { + size_t stackbase = L2->base - L2->stack; + size_t stacktop = L2->top - L2->stack; + lua_assert(L2->nCcalls <= 1); + pi->writer(pi->L, &L2->status, sizeof(lu_byte), pi->ud); + pi->writer(pi->L, &stackbase, sizeof(size_t), pi->ud); + pi->writer(pi->L, &stacktop, sizeof(size_t), pi->ud); + pi->writer(pi->L, &L2->errfunc, sizeof(ptrdiff_t), pi->ud); + } + + /* Finally, record upvalues which need to be reopened */ + /* See the comment above persistupval() for why we do this */ + { + GCObject *gco; + UpVal *uv; + /* perms reftbl ... thr */ + for(gco = L2->openupval; gco != NULL; gco = uv->next) { + size_t stackpos; + uv = gco2uv(gco); + + /* Make sure upvalue is really open */ + lua_assert(uv->v != &uv->u.value); + pushupval(pi->L, uv); + /* perms reftbl ... thr uv */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... thr */ + stackpos = uv->v - L2->stack; + pi->writer(pi->L, &stackpos, sizeof(size_t), pi->ud); + } + /* perms reftbl ... thr */ + lua_pushnil(pi->L); + /* perms reftbl ... thr nil */ + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... thr */ + } + /* perms reftbl ... thr */ +} + +static void persistboolean(PersistInfo *pi) +{ + int b = lua_toboolean(pi->L, -1); + pi->writer(pi->L, &b, sizeof(int), pi->ud); +} + +static void persistlightuserdata(PersistInfo *pi) +{ + void *p = lua_touserdata(pi->L, -1); + pi->writer(pi->L, &p, sizeof(void *), pi->ud); +} + +static void persistnumber(PersistInfo *pi) +{ + lua_Number n = lua_tonumber(pi->L, -1); + pi->writer(pi->L, &n, sizeof(lua_Number), pi->ud); +} + +static void persiststring(PersistInfo *pi) +{ + size_t length = lua_strlen(pi->L, -1); + pi->writer(pi->L, &length, sizeof(size_t), pi->ud); + pi->writer(pi->L, lua_tostring(pi->L, -1), length, pi->ud); +} + +/* Top-level delegating persist function + */ +static void persist(PersistInfo *pi) +{ + /* perms reftbl ... obj */ + lua_checkstack(pi->L, 2); + /* If the object has already been written, write a reference to it */ + lua_pushvalue(pi->L, -1); + /* perms reftbl ... obj obj */ + lua_rawget(pi->L, 2); + /* perms reftbl ... obj ref? */ + if(!lua_isnil(pi->L, -1)) { + /* perms reftbl ... obj ref */ + int zero = 0; + // FIXME: Casting a pointer to an integer data type is a bad idea we + // should really get rid of this by fixing the design of this code. + // For now casting to size_t should silence most (all?) compilers, + // since size_t is supposedly the same size as a pointer on most + // (modern) architectures. + int ref = (int)(size_t)lua_touserdata(pi->L, -1); + pi->writer(pi->L, &zero, sizeof(int), pi->ud); + pi->writer(pi->L, &ref, sizeof(int), pi->ud); + lua_pop(pi->L, 1); + /* perms reftbl ... obj ref */ +#ifdef PLUTO_DEBUG + printindent(pi->level); + printf("0 %d\n", ref); +#endif + return; + } + /* perms reftbl ... obj nil */ + lua_pop(pi->L, 1); + /* perms reftbl ... obj */ + /* If the object is nil, write the pseudoreference 0 */ + if(lua_isnil(pi->L, -1)) { + int zero = 0; + /* firsttime */ + pi->writer(pi->L, &zero, sizeof(int), pi->ud); + /* ref */ + pi->writer(pi->L, &zero, sizeof(int), pi->ud); +#ifdef PLUTO_DEBUG + printindent(pi->level); + printf("0 0\n"); +#endif + return; + } + { + /* indicate that it's the first time */ + int one = 1; + pi->writer(pi->L, &one, sizeof(int), pi->ud); + } + lua_pushvalue(pi->L, -1); + /* perms reftbl ... obj obj */ + lua_pushlightuserdata(pi->L, (void*)(++(pi->counter))); + /* perms reftbl ... obj obj ref */ + lua_rawset(pi->L, 2); + /* perms reftbl ... obj */ + + pi->writer(pi->L, &pi->counter, sizeof(int), pi->ud); + + + /* At this point, we'll give the permanents table a chance to play. */ + { + lua_pushvalue(pi->L, -1); + /* perms reftbl ... obj obj */ + lua_gettable(pi->L, 1); + /* perms reftbl ... obj permkey? */ + if(!lua_isnil(pi->L, -1)) { + /* perms reftbl ... obj permkey */ + int type = PLUTO_TPERMANENT; +#ifdef PLUTO_DEBUG + printindent(pi->level); + printf("1 %d PERM\n", pi->counter); + pi->level++; +#endif + pi->writer(pi->L, &type, sizeof(int), pi->ud); + persist(pi); + lua_pop(pi->L, 1); + /* perms reftbl ... obj */ +#ifdef PLUTO_DEBUG + pi->level--; +#endif + return; + } else { + /* perms reftbl ... obj nil */ + lua_pop(pi->L, 1); + /* perms reftbl ... obj */ + } + /* perms reftbl ... obj */ + } + { + int type = lua_type(pi->L, -1); + pi->writer(pi->L, &type, sizeof(int), pi->ud); + +#ifdef PLUTO_DEBUG + printindent(pi->level); + printf("1 %d %d\n", pi->counter, type); + pi->level++; +#endif + } + + switch(lua_type(pi->L, -1)) { + case LUA_TBOOLEAN: + persistboolean(pi); + break; + case LUA_TLIGHTUSERDATA: + persistlightuserdata(pi); + break; + case LUA_TNUMBER: + persistnumber(pi); + break; + case LUA_TSTRING: + persiststring(pi); + break; + case LUA_TTABLE: + persisttable(pi); + break; + case LUA_TFUNCTION: + persistfunction(pi); + break; + case LUA_TTHREAD: + persistthread(pi); + break; + case LUA_TPROTO: + persistproto(pi); + break; + case LUA_TUPVAL: + persistupval(pi); + break; + case LUA_TUSERDATA: + persistuserdata(pi); + break; + default: + lua_assert(0); + } +#ifdef PLUTO_DEBUG + pi->level--; +#endif +} + +void pluto_persist(lua_State *L, lua_Chunkwriter writer, void *ud) +{ + PersistInfo pi; + + pi.counter = 0; + pi.L = L; + pi.writer = writer; + pi.ud = ud; +#ifdef PLUTO_DEBUG + pi.level = 0; +#endif + + lua_checkstack(L, 4); + /* perms? rootobj? ...? */ + lua_assert(lua_gettop(L) == 2); + /* perms rootobj */ + lua_assert(!lua_isnil(L, 2)); + /* perms rootobj */ + lua_newtable(L); + /* perms rootobj reftbl */ + + /* Now we're going to make the table weakly keyed. This prevents the + * GC from visiting it and trying to mark things it doesn't want to + * mark in tables, e.g. upvalues. All objects in the table are + * a priori reachable, so it doesn't matter that we do this. */ + lua_newtable(L); + /* perms rootobj reftbl mt */ + lua_pushstring(L, "__mode"); + /* perms rootobj reftbl mt "__mode" */ + lua_pushstring(L, "k"); + /* perms rootobj reftbl mt "__mode" "k" */ + lua_settable(L, 4); + /* perms rootobj reftbl mt */ + lua_setmetatable(L, 3); + /* perms rootobj reftbl */ + lua_insert(L, 2); + /* perms reftbl rootobj */ + persist(&pi); + /* perms reftbl rootobj */ + lua_remove(L, 2); + /* perms rootobj */ +} + +typedef struct WriterInfo_t { + char* buf; + size_t buflen; +} WriterInfo; + +static int bufwriter (lua_State *L, const void* p, size_t sz, void* ud) { + const char* cp = (const char*)p; + WriterInfo *wi = (WriterInfo *)ud; + + LIF(M,reallocvector)(L, wi->buf, wi->buflen, wi->buflen+sz, char); + while(sz) + { + /* how dearly I love ugly C pointer twiddling */ + wi->buf[wi->buflen++] = *cp++; + sz--; + } + return 0; +} + +int persist_l(lua_State *L) +{ + /* perms? rootobj? ...? */ + WriterInfo wi; + + wi.buf = NULL; + wi.buflen = 0; + + lua_settop(L, 2); + /* perms? rootobj? */ + luaL_checktype(L, 1, LUA_TTABLE); + /* perms rootobj? */ + luaL_checktype(L, 1, LUA_TTABLE); + /* perms rootobj */ + + pluto_persist(L, bufwriter, &wi); + + lua_settop(L, 0); + /* (empty) */ + lua_pushlstring(L, wi.buf, wi.buflen); + /* str */ + pdep_freearray(L, wi.buf, wi.buflen, char); + return 1; +} + +typedef struct UnpersistInfo_t { + lua_State *L; + ZIO zio; +#ifdef PLUTO_DEBUG + int level; +#endif +} UnpersistInfo; + +static void unpersist(UnpersistInfo *upi); + +/* The object is left on the stack. This is primarily used by unpersist, but + * may be used by GCed objects that may incur cycles in order to preregister + * the object. */ +static void registerobject(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... obj */ + lua_checkstack(upi->L, 2); + lua_pushlightuserdata(upi->L, (void*)ref); + /* perms reftbl ... obj ref */ + lua_pushvalue(upi->L, -2); + /* perms reftbl ... obj ref obj */ + lua_settable(upi->L, 2); + /* perms reftbl ... obj */ +} + +static void unpersistboolean(UnpersistInfo *upi) +{ + /* perms reftbl ... */ + int b; + lua_checkstack(upi->L, 1); + verify(LIF(Z,read)(&upi->zio, &b, sizeof(int)) == 0); + lua_pushboolean(upi->L, b); + /* perms reftbl ... bool */ +} + +static void unpersistlightuserdata(UnpersistInfo *upi) +{ + /* perms reftbl ... */ + void *p; + lua_checkstack(upi->L, 1); + verify(LIF(Z,read)(&upi->zio, &p, sizeof(void *)) == 0); + lua_pushlightuserdata(upi->L, p); + /* perms reftbl ... ludata */ +} + +static void unpersistnumber(UnpersistInfo *upi) +{ + /* perms reftbl ... */ + lua_Number n; + lua_checkstack(upi->L, 1); + verify(LIF(Z,read)(&upi->zio, &n, sizeof(lua_Number)) == 0); + lua_pushnumber(upi->L, n); + /* perms reftbl ... num */ +} + +static void unpersiststring(UnpersistInfo *upi) +{ + /* perms reftbl sptbl ref */ + int length; + char* string; + lua_checkstack(upi->L, 1); + verify(LIF(Z,read)(&upi->zio, &length, sizeof(int)) == 0); + string = pdep_newvector(upi->L, length, char); + verify(LIF(Z,read)(&upi->zio, string, length) == 0); + lua_pushlstring(upi->L, string, length); + /* perms reftbl sptbl ref str */ + pdep_freearray(upi->L, string, length, char); +} + +static void unpersistspecialtable(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... */ + lua_checkstack(upi->L, 1); + unpersist(upi); + /* perms reftbl ... spfunc? */ + lua_assert(lua_isfunction(upi->L, -1)); + /* perms reftbl ... spfunc */ + lua_call(upi->L, 0, 1); + /* perms reftbl ... tbl? */ + lua_assert(lua_istable(upi->L, -1)); + /* perms reftbl ... tbl */ +} + +static void unpersistliteraltable(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... */ + lua_checkstack(upi->L, 3); + /* Preregister table for handling of cycles */ + lua_newtable(upi->L); + /* perms reftbl ... tbl */ + registerobject(ref, upi); + /* perms reftbl ... tbl */ + /* Unpersist metatable */ + { + unpersist(upi); + /* perms reftbl ... tbl mt/nil? */ + if(lua_istable(upi->L, -1)) { + /* perms reftbl ... tbl mt */ + lua_setmetatable(upi->L, -2); + /* perms reftbl ... tbl */ + } else { + /* perms reftbl ... tbl nil? */ + lua_assert(lua_isnil(upi->L, -1)); + /* perms reftbl ... tbl nil */ + lua_pop(upi->L, 1); + /* perms reftbl ... tbl */ + } + /* perms reftbl ... tbl */ + } + + while(1) + { + /* perms reftbl ... tbl */ + unpersist(upi); + /* perms reftbl ... tbl key/nil */ + if(lua_isnil(upi->L, -1)) { + /* perms reftbl ... tbl nil */ + lua_pop(upi->L, 1); + /* perms reftbl ... tbl */ + break; + } + /* perms reftbl ... tbl key */ + unpersist(upi); + /* perms reftbl ... tbl key value? */ + lua_assert(!lua_isnil(upi->L, -1)); + /* perms reftbl ... tbl key value */ + lua_rawset(upi->L, -3); + /* perms reftbl ... tbl */ + } +} + +static void unpersisttable(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... */ + lua_checkstack(upi->L, 1); + { + int isspecial; + verify(LIF(Z,read)(&upi->zio, &isspecial, sizeof(int)) == 0); + if(isspecial) { + unpersistspecialtable(ref, upi); + /* perms reftbl ... tbl */ + } else { + unpersistliteraltable(ref, upi); + /* perms reftbl ... tbl */ + } + /* perms reftbl ... tbl */ + } +} + +static UpVal *makeupval(lua_State *L, int stackpos) +{ + UpVal *uv = pdep_new(L, UpVal); + pdep_link(L, (GCObject*)uv, LUA_TUPVAL); + uv->tt = LUA_TUPVAL; + uv->v = &uv->u.value; + uv->u.l.prev = NULL; + uv->u.l.next = NULL; + setobj(L, uv->v, getobject(L, stackpos)); + return uv; +} + +static Proto *makefakeproto(lua_State *L, lu_byte nups) +{ + Proto *p = pdep_newproto(L); + p->sizelineinfo = 1; + p->lineinfo = pdep_newvector(L, 1, int); + p->lineinfo[0] = 1; + p->sizecode = 1; + p->code = pdep_newvector(L, 1, Instruction); + p->code[0] = CREATE_ABC(OP_RETURN, 0, 1, 0); + p->source = pdep_newlstr(L, "", 0); + p->maxstacksize = 2; + p->nups = nups; + p->sizek = 0; + p->sizep = 0; + + return p; +} + +/* The GC is not fond of finding upvalues in tables. We get around this + * during persistence using a weakly keyed table, so that the GC doesn't + * bother to mark them. This won't work in unpersisting, however, since + * if we make the values weak they'll be collected (since nothing else + * references them). Our solution, during unpersisting, is to represent + * upvalues as dummy functions, each with one upvalue. */ +static void boxupval_start(lua_State *L) +{ + LClosure *lcl; + lcl = (LClosure*)pdep_newLclosure(L, 1, hvalue(&L->l_gt)); + pushclosure(L, (Closure*)lcl); + /* ... func */ + lcl->p = makefakeproto(L, 1); + + /* Temporarily initialize the upvalue to nil */ + + lua_pushnil(L); + lcl->upvals[0] = makeupval(L, -1); + lua_pop(L, 1); +} + +static void boxupval_finish(lua_State *L) +{ + /* ... func obj */ + LClosure *lcl = (LClosure *) clvalue(getobject(L, -2)); + + lcl->upvals[0]->u.value = *getobject(L, -1); + lua_pop(L, 1); +} + + +static void unboxupval(lua_State *L) +{ + /* ... func */ + LClosure *lcl; + UpVal *uv; + + lcl = (LClosure*)clvalue(getobject(L, -1)); + uv = lcl->upvals[0]; + lua_pop(L, 1); + /* ... */ + pushupval(L, uv); + /* ... upval */ +} + +static void unpersistfunction(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... */ + LClosure *lcl; + int i; + lu_byte nupvalues; + lua_checkstack(upi->L, 2); + + verify(LIF(Z,read)(&upi->zio, &nupvalues, sizeof(lu_byte)) == 0); + + lcl = (LClosure*)pdep_newLclosure(upi->L, nupvalues, hvalue(&upi->L->l_gt)); + pushclosure(upi->L, (Closure*)lcl); + + /* perms reftbl ... func */ + /* Put *some* proto in the closure, before the GC can find it */ + lcl->p = makefakeproto(upi->L, nupvalues); + + /* Also, we need to temporarily fill the upvalues */ + lua_pushnil(upi->L); + /* perms reftbl ... func nil */ + for(i=0; i<nupvalues; i++) { + lcl->upvals[i] = makeupval(upi->L, -1); + } + lua_pop(upi->L, 1); + /* perms reftbl ... func */ + + /* I can't see offhand how a function would ever get to be self- + * referential, but just in case let's register it early */ + registerobject(ref, upi); + + /* Now that it's safe, we can get the real proto */ + unpersist(upi); + /* perms reftbl ... func proto? */ + lua_assert(lua_type(upi->L, -1) == LUA_TPROTO); + /* perms reftbl ... func proto */ + lcl->p = toproto(upi->L, -1); + lua_pop(upi->L, 1); + /* perms reftbl ... func */ + + for(i=0; i<nupvalues; i++) { + /* perms reftbl ... func */ + unpersist(upi); + /* perms reftbl ... func func2 */ + unboxupval(upi->L); + /* perms reftbl ... func upval */ + lcl->upvals[i] = toupval(upi->L, -1); + lua_pop(upi->L, 1); + /* perms reftbl ... func */ + } + /* perms reftbl ... func */ + + /* Finally, the fenv */ + unpersist(upi); + /* perms reftbl ... func fenv/nil? */ + lua_assert(lua_type(upi->L, -1) == LUA_TNIL || + lua_type(upi->L, -1) == LUA_TTABLE); + /* perms reftbl ... func fenv/nil */ + if(!lua_isnil(upi->L, -1)) { + /* perms reftbl ... func fenv */ + lua_setfenv(upi->L, -2); + /* perms reftbl ... func */ + } else { + /* perms reftbl ... func nil */ + lua_pop(upi->L, 1); + /* perms reftbl ... func */ + } + /* perms reftbl ... func */ +} + +static void unpersistupval(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... */ + lua_checkstack(upi->L, 2); + + boxupval_start(upi->L); + /* perms reftbl ... func */ + registerobject(ref, upi); + + unpersist(upi); + /* perms reftbl ... func obj */ + boxupval_finish(upi->L); + /* perms reftbl ... func */ +} + +static void unpersistproto(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... */ + Proto *p; + int i; + int sizep, sizek; + + /* We have to be careful. The GC expects a lot out of protos. In + * particular, we need to give the function a valid string for its + * source, and valid code, even before we actually read in the real + * code. */ + TString *source = pdep_newlstr(upi->L, "", 0); + p = pdep_newproto(upi->L); + p->source = source; + p->sizecode=1; + p->code = pdep_newvector(upi->L, 1, Instruction); + p->code[0] = CREATE_ABC(OP_RETURN, 0, 1, 0); + p->maxstacksize = 2; + p->sizek = 0; + p->sizep = 0; + + lua_checkstack(upi->L, 2); + + pushproto(upi->L, p); + /* perms reftbl ... proto */ + /* We don't need to register early, since protos can never ever be + * involved in cyclic references */ + + /* Read in constant references */ + { + verify(LIF(Z,read)(&upi->zio, &sizek, sizeof(int)) == 0); + LIF(M,reallocvector)(upi->L, p->k, 0, sizek, TValue); + for(i=0; i<sizek; i++) { + /* perms reftbl ... proto */ + unpersist(upi); + /* perms reftbl ... proto k */ + setobj2s(upi->L, &p->k[i], getobject(upi->L, -1)); + p->sizek++; + lua_pop(upi->L, 1); + /* perms reftbl ... proto */ + } + /* perms reftbl ... proto */ + } + /* Read in sub-proto references */ + { + verify(LIF(Z,read)(&upi->zio, &sizep, sizeof(int)) == 0); + LIF(M,reallocvector)(upi->L, p->p, 0, sizep, Proto*); + for(i=0; i<sizep; i++) { + /* perms reftbl ... proto */ + unpersist(upi); + /* perms reftbl ... proto subproto */ + p->p[i] = toproto(upi->L, -1); + p->sizep++; + lua_pop(upi->L, 1); + /* perms reftbl ... proto */ + } + /* perms reftbl ... proto */ + } + + /* Read in code */ + { + verify(LIF(Z,read)(&upi->zio, &p->sizecode, sizeof(int)) == 0); + LIF(M,reallocvector)(upi->L, p->code, 1, p->sizecode, Instruction); + verify(LIF(Z,read)(&upi->zio, p->code, + sizeof(Instruction) * p->sizecode) == 0); + } + + /* Read in upvalue names */ + { + verify(LIF(Z,read)(&upi->zio, &p->sizeupvalues, sizeof(int)) == 0); + if (p->sizeupvalues) + { + LIF(M,reallocvector)(upi->L, p->upvalues, 0, p->sizeupvalues, TString *); + for(i=0; i<p->sizeupvalues; i++) + { + unpersist(upi); + p->upvalues[i] = pdep_newlstr(upi->L, lua_tostring(upi->L, -1), strlen(lua_tostring(upi->L, -1))); + lua_pop(upi->L, 1); + } + } + } + + /* Read in local variable infos */ + { + verify(LIF(Z,read)(&upi->zio, &p->sizelocvars, sizeof(int)) == 0); + if (p->sizelocvars) + { + LIF(M,reallocvector)(upi->L, p->locvars, 0, p->sizelocvars, LocVar); + for(i=0; i<p->sizelocvars; i++) + { + unpersist(upi); + p->locvars[i].varname = pdep_newlstr(upi->L, lua_tostring(upi->L, -1), strlen(lua_tostring(upi->L, -1))); + lua_pop(upi->L, 1); + + verify(LIF(Z,read)(&upi->zio, &p->locvars[i].startpc, sizeof(int)) == 0); + verify(LIF(Z,read)(&upi->zio, &p->locvars[i].endpc, sizeof(int)) == 0); + } + } + } + + /* Read in source string*/ + unpersist(upi); + p->source = pdep_newlstr(upi->L, lua_tostring(upi->L, -1), strlen(lua_tostring(upi->L, -1))); + lua_pop(upi->L, 1); + + /* Read in line numbers */ + { + verify(LIF(Z,read)(&upi->zio, &p->sizelineinfo, sizeof(int)) == 0); + if (p->sizelineinfo) + { + LIF(M,reallocvector)(upi->L, p->lineinfo, 0, p->sizelineinfo, int); + verify(LIF(Z,read)(&upi->zio, p->lineinfo, + sizeof(int) * p->sizelineinfo) == 0); + } + } + + /* Read in linedefined and lastlinedefined */ + verify(LIF(Z,read)(&upi->zio, &p->linedefined, sizeof(int)) == 0); + verify(LIF(Z,read)(&upi->zio, &p->lastlinedefined, sizeof(int)) == 0); + + /* Read in misc values */ + { + verify(LIF(Z,read)(&upi->zio, &p->nups, sizeof(lu_byte)) == 0); + verify(LIF(Z,read)(&upi->zio, &p->numparams, sizeof(lu_byte)) == 0); + verify(LIF(Z,read)(&upi->zio, &p->is_vararg, sizeof(lu_byte)) == 0); + verify(LIF(Z,read)(&upi->zio, &p->maxstacksize, sizeof(lu_byte)) == 0); + } +} + + +/* Does basically the opposite of luaC_link(). + * Right now this function is rather inefficient; it requires traversing the + * entire root GC set in order to find one object. If the GC list were doubly + * linked this would be much easier, but there's no reason for Lua to have + * that. */ +static void gcunlink(lua_State *L, GCObject *gco) +{ + GCObject *prevslot; + if(G(L)->rootgc == gco) { + G(L)->rootgc = G(L)->rootgc->gch.next; + return; + } + + prevslot = G(L)->rootgc; + while(prevslot->gch.next != gco) { + lua_assert(prevslot->gch.next != NULL); + prevslot = prevslot->gch.next; + } + + prevslot->gch.next = prevslot->gch.next->gch.next; +} + +/* FIXME __ALL__ field ordering */ +static void unpersistthread(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... */ + lua_State *L2; + size_t stacklimit = 0; + L2 = lua_newthread(upi->L); + lua_checkstack(upi->L, 3); + /* L1: perms reftbl ... thr */ + /* L2: (empty) */ + registerobject(ref, upi); + + /* First, deserialize the object stack. */ + { + size_t i, stacksize; + verify(LIF(Z,read)(&upi->zio, &stacksize, sizeof(size_t)) == 0); + LIF(D,growstack)(L2, (int)stacksize); + /* Make sure that the first stack element (a nil, representing + * the imaginary top-level C function) is written to the very, + * very bottom of the stack */ + L2->top--; + for(i=0; i<stacksize; i++) { + unpersist(upi); + /* L1: perms reftbl ... thr obj* */ + } + lua_xmove(upi->L, L2, stacksize); + /* L1: perms reftbl ... thr */ + /* L2: obj* */ + } + /* (hereafter, stacks refer to L1) */ + + /* Now, deserialize the CallInfo stack. */ + { + size_t i, numframes; + verify(LIF(Z,read)(&upi->zio, &numframes, sizeof(size_t)) == 0); + LIF(D,reallocCI)(L2,numframes*2); + for(i=0; i<numframes; i++) { + CallInfo *ci = L2->base_ci + i; + size_t stackbase, stackfunc, stacktop, savedpc; + verify(LIF(Z,read)(&upi->zio, &stackbase, sizeof(size_t)) == 0); + verify(LIF(Z,read)(&upi->zio, &stackfunc, sizeof(size_t)) == 0); + verify(LIF(Z,read)(&upi->zio, &stacktop, sizeof(size_t)) == 0); + verify(LIF(Z,read)(&upi->zio, &ci->nresults, sizeof(int)) == 0); + verify(LIF(Z,read)(&upi->zio, &savedpc, sizeof(size_t)) == 0); + + if(stacklimit < stacktop) + stacklimit = stacktop; + + ci->base = L2->stack+stackbase; + ci->func = L2->stack+stackfunc; + ci->top = L2->stack+stacktop; + ci->savedpc = (ci != L2->base_ci) ? + ci_func(ci)->l.p->code+savedpc : + 0; + ci->tailcalls = 0; + /* Update the pointer each time, to keep the GC + * happy*/ + L2->ci = ci; + } + } + /* perms reftbl ... thr */ + /* Deserialize the state's other parameters, with the exception of upval stuff */ + { + size_t stackbase, stacktop; + L2->savedpc = L2->ci->savedpc; + verify(LIF(Z,read)(&upi->zio, &L2->status, sizeof(lu_byte)) == 0); + verify(LIF(Z,read)(&upi->zio, &stackbase, sizeof(size_t)) == 0); + verify(LIF(Z,read)(&upi->zio, &stacktop, sizeof(size_t)) == 0); + verify(LIF(Z,read)(&upi->zio, &L2->errfunc, sizeof(ptrdiff_t)) == 0); + L2->base = L2->stack + stackbase; + L2->top = L2->stack + stacktop; + } + /* Finally, "reopen" upvalues (see persistupval() for why) */ + { + UpVal* uv; + GCObject **nextslot = &L2->openupval; + global_State *g = G(L2); + while(1) { + size_t stackpos; + unpersist(upi); + /* perms reftbl ... thr uv/nil */ + if(lua_isnil(upi->L, -1)) { + /* perms reftbl ... thr nil */ + lua_pop(upi->L, 1); + /* perms reftbl ... thr */ + break; + } + /* perms reftbl ... thr boxeduv */ + unboxupval(upi->L); + /* perms reftbl ... thr uv */ + uv = toupval(upi->L, -1); + lua_pop(upi->L, 1); + /* perms reftbl ... thr */ + + verify(LIF(Z,read)(&upi->zio, &stackpos, sizeof(size_t)) == 0); + uv->v = L2->stack + stackpos; + gcunlink(upi->L, (GCObject*)uv); + uv->marked = luaC_white(g); + *nextslot = (GCObject*)uv; + nextslot = &uv->next; + uv->u.l.prev = &G(L2)->uvhead; + uv->u.l.next = G(L2)->uvhead.u.l.next; + uv->u.l.next->u.l.prev = uv; + G(L2)->uvhead.u.l.next = uv; + lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + } + *nextslot = NULL; + } + + /* The stack must be valid at least to the highest value among the CallInfos */ + /* 'top' and the values up to there must be filled with 'nil' */ + { + StkId o; + LIF(D,checkstack)(L2, (int)stacklimit); + for (o = L2->top; o <= L2->top + stacklimit; o++) + setnilvalue(o); + } +} + +static void unpersistuserdata(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... */ + int isspecial; + lua_checkstack(upi->L, 2); + verify(LIF(Z,read)(&upi->zio, &isspecial, sizeof(int)) == 0); + if(isspecial) { + unpersist(upi); + /* perms reftbl ... spfunc? */ + lua_assert(lua_isfunction(upi->L, -1)); + /* perms reftbl ... spfunc */ +#ifdef PLUTO_PASS_USERDATA_TO_PERSIST + lua_pushlightuserdata(upi->L, &upi->zio); + lua_call(upi->L, 1, 1); +#else + lua_call(upi->L, 0, 1); +#endif + /* perms reftbl ... udata? */ +/* This assertion might not be necessary; it's conceivable, for + * example, that the SP function might decide to return a table + * with equivalent functionality. For the time being, we'll + * ignore this possibility in favor of stricter and more testable + * requirements. */ + lua_assert(lua_isuserdata(upi->L, -1)); + /* perms reftbl ... udata */ + } else { + size_t length; + verify(LIF(Z,read)(&upi->zio, &length, sizeof(size_t)) == 0); + + lua_newuserdata(upi->L, length); + /* perms reftbl ... udata */ + registerobject(ref, upi); + verify(LIF(Z,read)(&upi->zio, lua_touserdata(upi->L, -1), length) == 0); + + unpersist(upi); + /* perms reftbl ... udata mt/nil? */ + lua_assert(lua_istable(upi->L, -1) || lua_isnil(upi->L, -1)); + /* perms reftbl ... udata mt/nil */ + lua_setmetatable(upi->L, -2); + /* perms reftbl ... udata */ + } + /* perms reftbl ... udata */ +} + +static void unpersistpermanent(int ref, UnpersistInfo *upi) +{ + /* perms reftbl ... */ + lua_checkstack(upi->L, 2); + unpersist(upi); + /* perms reftbl permkey */ + lua_gettable(upi->L, 1); + /* perms reftbl perm? */ + /* We assume currently that the substituted permanent value + * shouldn't be nil. This may be a bad assumption. Real-life + * experience is needed to evaluate this. */ + lua_assert(!lua_isnil(upi->L, -1)); + /* perms reftbl perm */ +} + +#if 0 +/* For debugging only; not called when lua_assert is empty */ +static int inreftable(lua_State *L, int ref) +{ + int res; + lua_checkstack(L, 1); + /* perms reftbl ... */ + lua_pushlightuserdata(L, (void*)ref); + /* perms reftbl ... ref */ + lua_gettable(L, 2); + /* perms reftbl ... obj? */ + res = !lua_isnil(L, -1); + lua_pop(L, 1); + /* perms reftbl ... */ + return res; +} +#endif + +static void unpersist(UnpersistInfo *upi) +{ + /* perms reftbl ... */ + int firstTime; + int stacksize = lua_gettop(upi->L); stacksize = stacksize; /* DEBUG */ + lua_checkstack(upi->L, 2); + LIF(Z,read)(&upi->zio, &firstTime, sizeof(int)); + if(firstTime) { + int ref; + int type; + LIF(Z,read)(&upi->zio, &ref, sizeof(int)); + lua_assert(!inreftable(upi->L, ref)); + LIF(Z,read)(&upi->zio, &type, sizeof(int)); +#ifdef PLUTO_DEBUG + printindent(upi->level); + printf("1 %d %d\n", ref, type); + upi->level++; +#endif + switch(type) { + case LUA_TBOOLEAN: + unpersistboolean(upi); + break; + case LUA_TLIGHTUSERDATA: + unpersistlightuserdata(upi); + break; + case LUA_TNUMBER: + unpersistnumber(upi); + break; + case LUA_TSTRING: + unpersiststring(upi); + break; + case LUA_TTABLE: + unpersisttable(ref, upi); + break; + case LUA_TFUNCTION: + unpersistfunction(ref, upi); + break; + case LUA_TTHREAD: + unpersistthread(ref, upi); + break; + case LUA_TPROTO: + unpersistproto(ref, upi); + break; + case LUA_TUPVAL: + unpersistupval(ref, upi); + break; + case LUA_TUSERDATA: + unpersistuserdata(ref, upi); + break; + case PLUTO_TPERMANENT: + unpersistpermanent(ref, upi); + break; + default: + lua_assert(0); + } + /* perms reftbl ... obj */ + lua_assert(lua_type(upi->L, -1) == type || + type == PLUTO_TPERMANENT || + /* Remember, upvalues get a special dispensation, as + * described in boxupval */ + (lua_type(upi->L, -1) == LUA_TFUNCTION && + type == LUA_TUPVAL)); + registerobject(ref, upi); + /* perms reftbl ... obj */ +#ifdef PLUTO_DEBUG + upi->level--; +#endif + } else { + int ref; + LIF(Z,read)(&upi->zio, &ref, sizeof(int)); +#ifdef PLUTO_DEBUG + printindent(upi->level); + printf("0 %d\n", ref); +#endif + if(ref == 0) { + lua_pushnil(upi->L); + /* perms reftbl ... nil */ + } else { + lua_pushlightuserdata(upi->L, (void*)ref); + /* perms reftbl ... ref */ + lua_gettable(upi->L, 2); + /* perms reftbl ... obj? */ + lua_assert(!lua_isnil(upi->L, -1)); + } + /* perms reftbl ... obj/nil */ + } + /* perms reftbl ... obj/nil */ + lua_assert(lua_gettop(upi->L) == stacksize + 1); +} + +void pluto_unpersist(lua_State *L, lua_Chunkreader reader, void *ud) +{ + /* We use the graciously provided ZIO (what the heck does the Z stand + * for?) library so that we don't have to deal with the reader directly. + * Letting the reader function decide how much data to return can be + * very unpleasant. + */ + UnpersistInfo upi; + upi.L = L; +#ifdef PLUTO_DEBUG + upi.level = 0; +#endif + + lua_checkstack(L, 3); + LIF(Z,init)(L, &upi.zio, reader, ud); + + /* perms */ + lua_newtable(L); + /* perms reftbl */ + lua_gc(L, LUA_GCSTOP, 0); + unpersist(&upi); + lua_gc(L, LUA_GCRESTART, 0); + /* perms reftbl rootobj */ + lua_replace(L, 2); + /* perms rootobj */ +} + +typedef struct LoadInfo_t { + char *buf; + size_t size; +} LoadInfo; + + +static const char *bufreader(lua_State *L, void *ud, size_t *sz) { + LoadInfo *li = (LoadInfo *)ud; + if(li->size == 0) { + return NULL; + } + *sz = li->size; + li->size = 0; + return li->buf; +} + +int unpersist_l(lua_State *L) +{ + LoadInfo li; + char const *origbuf; + char *tempbuf; + size_t bufsize; + /* perms? str? ...? */ + lua_settop(L, 2); + /* perms? str? */ + origbuf = luaL_checklstring(L, 2, &bufsize); + tempbuf = LIF(M,newvector)(L, bufsize, char); + memcpy(tempbuf, origbuf, bufsize); + + li.buf = tempbuf; + li.size = bufsize; + + /* perms? str */ + lua_pop(L, 1); + /* perms? */ + luaL_checktype(L, 1, LUA_TTABLE); + /* perms */ + pluto_unpersist(L, bufreader, &li); + /* perms rootobj */ + LIF(M,freearray)(L, tempbuf, bufsize, char); + return 1; +} + +static luaL_reg pluto_reg[] = { + { "persist", persist_l }, + { "unpersist", unpersist_l }, + { NULL, NULL } +}; + +LUALIB_API int luaopen_pluto(lua_State *L) { + luaL_openlib(L, "pluto", pluto_reg, 0); + return 1; +} diff --git a/engines/sword25/util/pluto/pluto.h b/engines/sword25/util/pluto/pluto.h new file mode 100644 index 0000000000..3674842d44 --- /dev/null +++ b/engines/sword25/util/pluto/pluto.h @@ -0,0 +1,25 @@ +/* $Id$ */ + +/* Pluto - Heavy-duty persistence for Lua + * Copyright (C) 2004 by Ben Sunshine-Hill, and released into the public + * domain. People making use of this software as part of an application + * are politely requested to email the author at sneftel@gmail.com + * with a brief description of the application, primarily to satisfy his + * curiosity. + * + * 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. + */ + +/* lua.h must be included before this file */ + +void pluto_persist(lua_State *L, lua_Chunkwriter writer, void *ud); + +void pluto_unpersist(lua_State *L, lua_Chunkreader reader, void *ud); + +LUALIB_API int luaopen_pluto(lua_State *L); diff --git a/engines/sword25/util/pluto/plzio.cpp b/engines/sword25/util/pluto/plzio.cpp new file mode 100644 index 0000000000..0efc3dfcf2 --- /dev/null +++ b/engines/sword25/util/pluto/plzio.cpp @@ -0,0 +1,76 @@ +/* +** $Id$ +** a generic input stream interface +** See Copyright Notice in lua.h +*/ + + +#include <string.h> + +#define lzio_c +#define LUA_CORE + +#include "pdep/pdep.h" + +int pdep_fill (ZIO *z) { + size_t size; + lua_State *L = z->L; + const char *buff; + lua_unlock(L); + buff = z->reader(L, z->data, &size); + lua_lock(L); + if (buff == NULL || size == 0) return EOZ; + z->n = size - 1; + z->p = buff; + return char2int(*(z->p++)); +} + + +int pdep_lookahead (ZIO *z) { + if (z->n == 0) { + if (pdep_fill(z) == EOZ) + return EOZ; + else { + z->n++; /* pdep_fill removed first byte; put back it */ + z->p--; + } + } + return char2int(*z->p); +} + + +void pdep_init (lua_State *L, ZIO *z, lua_Reader reader, void *data) { + z->L = L; + z->reader = reader; + z->data = data; + z->n = 0; + z->p = NULL; +} + + +/* --------------------------------------------------------------- read --- */ +size_t pdep_read (ZIO *z, void *b, size_t n) { + while (n) { + size_t m; + if (pdep_lookahead(z) == EOZ) + return n; /* return number of missing bytes */ + m = (n <= z->n) ? n : z->n; /* min. between n and z->n */ + memcpy(b, z->p, m); + z->n -= m; + z->p += m; + b = (char *)b + m; + n -= m; + } + return 0; +} + +/* ------------------------------------------------------------------------ */ +char *pdep_openspace (lua_State *L, Mbuffer *buff, size_t n) { + if (n > buff->buffsize) { + if (n < LUA_MINBUFFER) n = LUA_MINBUFFER; + pdep_resizebuffer(L, buff, n); + } + return buff->buffer; +} + + diff --git a/engines/sword25/util/pluto/pptest.cpp b/engines/sword25/util/pluto/pptest.cpp new file mode 100644 index 0000000000..1bfecf2b75 --- /dev/null +++ b/engines/sword25/util/pluto/pptest.cpp @@ -0,0 +1,95 @@ +/* $Id$ */ + +#include <stdio.h> +#include <stdlib.h> + +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + +static int LUAF_createludata(lua_State *L) +{ + lua_pushlightuserdata(L, (void*)321); + return 1; +} + +/* A userdata that may be literally persisted */ +static int LUAF_boxinteger(lua_State *L) +{ + /* num */ + int* ptr = lua_newuserdata(L, sizeof(int)); + /* num udata */ + *ptr = luaL_checkint(L, 1); + lua_newtable(L); + /* num udata mt */ + lua_pushstring(L, "__persist"); + /* num udata mt "__persist" */ + lua_pushboolean(L, 1); + /* num udata mt "__persist" true */ + lua_rawset(L, 3); + /* num udata mt */ + lua_setmetatable(L, 2); + /* num udata */ + return 1; +} + +static int LUAF_boxboolean(lua_State *L) +{ + /* bool */ + char* ptr = lua_newuserdata(L, sizeof(char)); + /* bool udata */ + *ptr = lua_toboolean(L, 1); + lua_newtable(L); + /* num udata mt */ + lua_pushstring(L, "__persist"); + /* num udata mt "__persist" */ + lua_getglobal(L, "booleanpersist"); + /* num udata mt "__persist" booleanpersist */ + lua_rawset(L, 3); + /* num udata mt */ + lua_setmetatable(L, 2); + /* num udata */ + return 1; +} + +static int LUAF_unboxboolean(lua_State *L) +{ + /* udata */ + lua_pushboolean(L, *(char*)lua_touserdata(L, 1)); + /* udata bool */ + return 1; +} + +static int LUAF_onerror(lua_State *L) +{ + + const char* str = 0; + if(lua_gettop(L) != 0) + { + str = lua_tostring(L, -1); + printf("%s\n",str); + } + return 0; +} + +int main() +{ + lua_State* L = lua_open(); + + luaL_openlibs(L); + lua_settop(L, 0); + + lua_register(L, "createludata", LUAF_createludata); + lua_register(L, "boxinteger", LUAF_boxinteger); + lua_register(L, "boxboolean", LUAF_boxboolean); + lua_register(L, "unboxboolean", LUAF_unboxboolean); + lua_register(L, "onerror", LUAF_onerror); + + lua_pushcfunction(L, LUAF_onerror); + luaL_loadfile(L, "pptest.lua"); + lua_pcall(L,0,0,1); + + lua_close(L); + + return 0; +} diff --git a/engines/sword25/util/pluto/pptest.lua b/engines/sword25/util/pluto/pptest.lua new file mode 100644 index 0000000000..144da3ee80 --- /dev/null +++ b/engines/sword25/util/pluto/pptest.lua @@ -0,0 +1,168 @@ +-- $Id$ + +require "pluto" + +permtable = { 1234 } + +perms = { [coroutine.yield] = 1, [permtable] = 2 } + +twithmt = {} +setmetatable( twithmt, { __call = function() return 21 end } ) + +function testfenv() + return abc +end + +setfenv(testfenv, { abc = 456 }) + +function fa(i) + local ia = i + 1 + return fb(ia) +end + +function fb(i) + local ib = i + 1 + ib = ib + fc(ib) + return ib +end + +function fc(i) + local ic = i + 1 + coroutine.yield() + return ic*2 +end + +function func() + return 4 +end + +thr = coroutine.create(fa) +coroutine.resume(thr, 2) + +testtbl = { a = 2, [2] = 4 } + +function funcreturningclosure(n) + return function() + return n + end +end + +function nestedfunc(n) + return (function(m) return m+2 end)(n+3) +end + +testloopa = {} +testloopb = { testloopa = testloopa } +testloopa.testloopb = testloopb + +sharedref = {} +refa = {sharedref = sharedref} +refb = {sharedref = sharedref} + +sptable = { a = 3 } + +setmetatable(sptable, { + __persist = function(tbl) + local a = tbl.a + return function() + return { a = a+3 } + end + end +}) + +literaludata = boxinteger(71) + +function booleanpersist(udata) + local b = unboxboolean(udata) + return function() + return boxboolean(b) + end +end + +function makecounter() + local a = 0 + return { + inc = function() a = a + 1 end, + cur = function() return a end + } +end + +function uvinthreadfunc() + local a = 1 + local b = function() + a = a+1 + coroutine.yield() + a = a+1 + end + a = a+1 + b() + a = a+1 + return a +end + +uvinthread = coroutine.create(uvinthreadfunc) +coroutine.resume(uvinthread) + +niinmt = { a = 3 } +setmetatable(niinmt, {__newindex = function(key, val) end }) + + + + +local function GenerateObjects() + local Table = {} + + function Table:Func() + return { Table, self } + end + + function uvcycle() + return Table:Func() + end +end + +GenerateObjects() + + + +function debuginfo(foo) + foo = foo + foo + return debug.getlocal(1,1) +end + +rootobj = { + testfalse = false, + testtrue = true, + testseven = 7, + testfoobar = "foobar", + testfuncreturnsfour = func, + testnil = nil, + testthread = thr, + testperm = permtable, + testmt = twithmt, + testtbl = testtbl, + testfenv = testfenv, + testclosure = funcreturningclosure(11), + testnilclosure = funcreturningclosure(nil), + testnest = nestedfunc, + testludata = createludata(), + testlooptable = testloopa, + testsharedrefa = refa, + testsharedrefb = refb, + testsptable = sptable, + testliteraludata = literaludata, + testspudata1 = boxboolean(true), + testspudata2 = boxboolean(false), + testsharedupval = makecounter(), + testuvinthread = uvinthread, + testniinmt = niinmt, + testuvcycle = uvcycle, + testdebuginfo = debuginfo +} + +buf = pluto.persist(perms, rootobj) + +onerror() +outfile = io.open("test.plh", "wb") +outfile:write(buf) +outfile:close() diff --git a/engines/sword25/util/pluto/puptest.cpp b/engines/sword25/util/pluto/puptest.cpp new file mode 100644 index 0000000000..e9aa7ea305 --- /dev/null +++ b/engines/sword25/util/pluto/puptest.cpp @@ -0,0 +1,81 @@ +/* $Id$ */ + +#include <stdio.h> +#include <stdlib.h> + +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + +static int LUAF_checkludata(lua_State *L) +{ + lua_pushboolean(L, lua_touserdata(L, -1) == (void*)321); + return 1; +} + +static int LUAF_unboxinteger(lua_State *L) +{ + lua_pushnumber(L, *((int*)lua_touserdata(L, -1))); + return 1; +} + +static int LUAF_unboxboolean(lua_State *L) +{ + /* udata */ + lua_pushboolean(L, *(char*)lua_touserdata(L, 1)); + /* udata bool */ + return 1; +} + +static int LUAF_boxboolean(lua_State *L) +{ + /* bool */ + char* ptr = lua_newuserdata(L, sizeof(char)); + /* bool udata */ + *ptr = lua_toboolean(L, 1); + lua_newtable(L); + /* num udata mt */ + lua_pushstring(L, "__persist"); + /* num udata mt "__persist" */ + lua_getglobal(L, "booleanpersist"); + /* num udata mt "__persist" booleanpersist */ + lua_rawset(L, 3); + /* num udata mt */ + lua_setmetatable(L, 2); + /* num udata */ + return 1; +} + +static int LUAF_onerror(lua_State *L) +{ + + const char* str = 0; + if(lua_gettop(L) != 0) + { + str = lua_tostring(L, -1); + printf("%s\n",str); + } + return 0; +} + +int main() +{ + lua_State* L = lua_open(); + + luaL_openlibs(L); + lua_settop(L, 0); + + lua_register(L, "checkludata", LUAF_checkludata); + lua_register(L, "unboxinteger", LUAF_unboxinteger); + lua_register(L, "boxboolean", LUAF_boxboolean); + lua_register(L, "unboxboolean", LUAF_unboxboolean); + lua_register(L, "onerror", LUAF_onerror); + + lua_pushcfunction(L, LUAF_onerror); + luaL_loadfile(L, "puptest.lua"); + lua_pcall(L,0,0,1); + + lua_close(L); + + return 0; +} diff --git a/engines/sword25/util/pluto/puptest.lua b/engines/sword25/util/pluto/puptest.lua new file mode 100644 index 0000000000..e5ccdd64bd --- /dev/null +++ b/engines/sword25/util/pluto/puptest.lua @@ -0,0 +1,93 @@ +-- $Id$ + +require "pluto" + +permtable = { 1234 } + +perms = { [1] = coroutine.yield, [2] = permtable } + +function testcounter(counter) + local a = counter.cur() + counter.inc() + return counter.cur() == a+1 +end + +function testuvinthread(func) + local success, result = coroutine.resume(func) + return success and result == 5 +end + + +function test(rootobj) + local passed = 0 + local total = 0 + local dotest = function(name,cond) + total = total+1 + if cond then + print(name, " PASSED") + passed = passed + 1 + else + print(name, "* FAILED") + end + end + + + dotest("Boolean FALSE ", rootobj.testfalse == false) + dotest("Boolean TRUE ", rootobj.testtrue == true) + dotest("Number 7 ", rootobj.testseven == 7) + dotest("String 'foobar' ", rootobj.testfoobar == "foobar") + dotest("Func returning 4 ", rootobj.testfuncreturnsfour() == 4) + dotest("Nil value ", rootobj.testnil == nil) + dotest("Thread resume ", coroutine.resume(rootobj.testthread) == true,14) + dotest("Table ", rootobj.testtbl.a == 2 and rootobj.testtbl[2] == 4); + dotest("Permanent table ", rootobj.testperm == permtable) + dotest("Table metatable ", rootobj.testmt() == 21) + dotest("Function env ", rootobj.testfenv() == 456) + dotest("Lua closure ", rootobj.testclosure() == 11) + dotest("Nil in closure ", rootobj.testnilclosure() == nil) + dotest("Nested func ", rootobj.testnest(1) == 6) + dotest("Light userdata ", checkludata(rootobj.testludata)) + dotest("Looped tables ", + rootobj.testlooptable.testloopb.testloopa == + rootobj.testlooptable) + dotest("Shared reference ", rootobj.testsharedrefa.sharedref == + rootobj.testsharedrefb.sharedref) + dotest("Identical tables ", rootobj.testsharedrefa ~= + rootobj.testsharedrefb) + dotest("Table special persist", rootobj.testsptable.a == 6) + dotest("Udata literal persist", + unboxinteger(rootobj.testliteraludata) == 71) + dotest("Udata special persist", + unboxboolean(rootobj.testspudata1) == true and + unboxboolean(rootobj.testspudata2) == false) + dotest("Shared upvalues ", + testcounter(rootobj.testsharedupval)) + dotest("Open upvalues ", + testuvinthread(rootobj.testuvinthread)) + dotest("Upvalue cycles ", + rootobj.testuvcycle()[1] == rootobj.testuvcycle()[2]) + dotest("__newindex metamethod", rootobj.testniinmt.a == 3) + dotest("Debug info ", (rootobj.testdebuginfo(2)) == "foo") + print() + if passed == total then + print("All tests passed.") + else + print(passed .. "/" .. total .. " tests passed.") + end +end + +infile, err = io.open("test.plh", "rb") +if infile == nil then + error("While opening: " .. (err or "no error")) +end + +buf, err = infile:read("*a") +if buf == nil then + error("While reading: " .. (err or "no error")) +end + +infile:close() + +rootobj = pluto.unpersist(perms, buf) + +test(rootobj) |