MTROPOLIS: Initial movie element support

This commit is contained in:
elasota 2022-04-27 20:08:15 -04:00 committed by Eugene Sandulenko
parent 18f0271beb
commit 6e53185f52
15 changed files with 572 additions and 56 deletions

View File

@ -49,6 +49,7 @@ namespace Common {
* @details File parser used in engines:
* - groovie
* - mohawk
* - mtropolis
* - sci
* @{
*/
@ -78,7 +79,7 @@ public:
/**
* Set the beginning offset of the video so we can modify the offsets in the stco
* atom of videos inside the Mohawk archives
* atom of videos inside the Mohawk/mTropolis archives
* @param offset the beginning offset of the video
*/
void setChunkBeginOffset(uint32 offset) { _beginOffset = offset; }

View File

@ -24,7 +24,7 @@
namespace MTropolis {
AssetLoaderContext::AssetLoaderContext() {
AssetLoaderContext::AssetLoaderContext(size_t streamIndex) : streamIndex(streamIndex) {
}
template<typename TAsset, typename TAssetData>

View File

@ -28,7 +28,9 @@
namespace MTropolis {
struct AssetLoaderContext {
AssetLoaderContext();
explicit AssetLoaderContext(size_t streamIndex);
size_t streamIndex;
};
struct IAssetFactory {

View File

@ -20,6 +20,7 @@
*/
#include "mtropolis/assets.h"
#include "mtropolis/asset_factory.h"
namespace MTropolis {
@ -29,6 +30,10 @@ Asset::Asset() : _assetID(0) {
Asset::~Asset() {
}
uint32 Asset::getAssetID() const {
return _assetID;
}
bool ColorTableAsset::load(AssetLoaderContext &context, const Data::ColorTableAsset &data) {
_assetID = data.assetID;
for (int i = 0; i < 256; i++) {
@ -39,6 +44,10 @@ bool ColorTableAsset::load(AssetLoaderContext &context, const Data::ColorTableAs
return true;
}
AssetType ColorTableAsset::getAssetType() const {
return kAssetTypeColorTable;
}
bool AudioAsset::load(AssetLoaderContext &context, const Data::AudioAsset &data) {
_sampleRate = data.sampleRate1;
_bitsPerSample = data.bitsPerSample;
@ -73,14 +82,45 @@ bool AudioAsset::load(AssetLoaderContext &context, const Data::AudioAsset &data)
return true;
}
AssetType AudioAsset::getAssetType() const {
return kAssetTypeAudio;
}
bool MovieAsset::load(AssetLoaderContext &context, const Data::MovieAsset &data) {
_assetID = data.assetID;
_moovAtomPos = data.moovAtomPos;
_movieDataPos = data.movieDataPos;
_movieDataSize = data.movieDataSize;
_extFileName = data.extFileName;
_streamIndex = context.streamIndex;
return true;
}
AssetType MovieAsset::getAssetType() const {
return kAssetTypeMovie;
}
uint32 MovieAsset::getMovieDataPos() const {
return _movieDataPos;
}
uint32 MovieAsset::getMoovAtomPos() const {
return _moovAtomPos;
}
uint32 MovieAsset::getMovieDataSize() const {
return _movieDataSize;
}
const Common::String &MovieAsset::getExtFileName() const {
return _extFileName;
}
size_t MovieAsset::getStreamIndex() const {
return _streamIndex;
}
} // End of namespace MTropolis

View File

@ -32,6 +32,7 @@ struct AssetLoaderContext;
class ColorTableAsset : public Asset {
public:
bool load(AssetLoaderContext &context, const Data::ColorTableAsset &data);
AssetType getAssetType() const override;
private:
ColorRGB8 _colors[256];
@ -51,6 +52,7 @@ public:
};
bool load(AssetLoaderContext &context, const Data::AudioAsset &data);
AssetType getAssetType() const override;
private:
uint16 _sampleRate;
@ -67,6 +69,14 @@ private:
class MovieAsset : public Asset {
public:
bool load(AssetLoaderContext &context, const Data::MovieAsset &data);
AssetType getAssetType() const override;
uint32 getMovieDataPos() const;
uint32 getMoovAtomPos() const;
uint32 getMovieDataSize() const;
const Common::String &getExtFileName() const;
size_t getStreamIndex() const;
private:
uint32 _movieDataPos;
@ -74,6 +84,7 @@ private:
uint32 _movieDataSize;
Common::String _extFileName;
size_t _streamIndex;
};
} // End of namespace MTropolis

View File

@ -95,7 +95,7 @@ void Debugger::notify(DebugSeverity severity, const Common::String& str) {
const Graphics::PixelFormat pixelFmt = _runtime->getRenderPixelFormat();
ToastNotification toastNotification;
toastNotification.window.reset(new Window(0, displayHeight, width, toastNotificationHeight, pixelFmt));
toastNotification.window.reset(new Window(_runtime, 0, displayHeight, width, toastNotificationHeight, pixelFmt));
byte fillColor[3] = {255, 255, 255};
if (severity == kDebugSeverityError) {
@ -165,7 +165,7 @@ void Debugger::refreshSceneStatus() {
const Graphics::PixelFormat pixelFmt = _runtime->getRenderPixelFormat();
_sceneStatusWindow.reset(new Window(0, 0, horizPadding * 2 + width, vertSpacing * sceneStrs.size(), pixelFmt));
_sceneStatusWindow.reset(new Window(_runtime, 0, 0, horizPadding * 2 + width, vertSpacing * sceneStrs.size(), pixelFmt));
_runtime->addWindow(_sceneStatusWindow);
for (uint i = 0; i < sceneStrs.size(); i++) {

View File

@ -25,7 +25,7 @@
namespace MTropolis {
ElementLoaderContext::ElementLoaderContext() {
ElementLoaderContext::ElementLoaderContext(Runtime *runtime, size_t streamIndex) : runtime(runtime), streamIndex(streamIndex) {
}
template<typename TElement, typename TElementData>

View File

@ -28,7 +28,10 @@
namespace MTropolis {
struct ElementLoaderContext {
ElementLoaderContext();
ElementLoaderContext(Runtime *runtime, size_t streamIndex);
Runtime *runtime;
size_t streamIndex;
};
struct IElementFactory {

View File

@ -20,10 +20,19 @@
*/
#include "mtropolis/elements.h"
#include "mtropolis/assets.h"
#include "mtropolis/element_factory.h"
#include "mtropolis/render.h"
#include "video/video_decoder.h"
#include "video/qt_decoder.h"
#include "common/substream.h"
#include "graphics/managed_surface.h"
namespace MTropolis {
GraphicElement::GraphicElement() : _directToScreen(false), _cacheBitmap(false) {
GraphicElement::GraphicElement() : _cacheBitmap(false) {
}
GraphicElement::~GraphicElement() {
@ -33,25 +42,29 @@ bool GraphicElement::load(ElementLoaderContext &context, const Data::GraphicElem
if (!loadCommon(data.name, data.guid, data.rect1, data.elementFlags, data.layer, data.streamLocator, data.sectionID))
return false;
_directToScreen = ((data.elementFlags & Data::ElementFlags::kNotDirectToScreen) == 0);
_cacheBitmap = ((data.elementFlags & Data::ElementFlags::kCacheBitmap) != 0);
return true;
}
void GraphicElement::render(Window *window) {
// todo
}
MovieElement::MovieElement()
: _directToScreen(false), _cacheBitmap(false), _paused(false)
, _loop(false), _alternate(false), _playEveryFrame(false), _assetID(0) {
: _cacheBitmap(false), _paused(false)
, _loop(false), _alternate(false), _playEveryFrame(false), _assetID(0), _runtime(nullptr) {
}
MovieElement::~MovieElement() {
if (_unloadSignaller)
_unloadSignaller->removeReceiver(this);
}
bool MovieElement::load(ElementLoaderContext &context, const Data::MovieElement &data) {
if (!loadCommon(data.name, data.guid, data.rect1, data.elementFlags, data.layer, data.streamLocator, data.sectionID))
return false;
_directToScreen = ((data.elementFlags & Data::ElementFlags::kNotDirectToScreen) == 0);
_cacheBitmap = ((data.elementFlags & Data::ElementFlags::kCacheBitmap) != 0);
_paused = ((data.elementFlags & Data::ElementFlags::kPaused) != 0);
_loop = ((data.animationFlags & Data::AnimationFlags::kLoop) != 0);
@ -59,15 +72,12 @@ bool MovieElement::load(ElementLoaderContext &context, const Data::MovieElement
_playEveryFrame = ((data.animationFlags & Data::AnimationFlags::kPlayEveryFrame) != 0);
_assetID = data.assetID;
_runtime = context.runtime;
return true;
}
bool MovieElement::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
if (attrib == "direct") {
result.setBool(_directToScreen);
return true;
}
if (attrib == "paused") {
result.setBool(_paused);
return true;
@ -77,10 +87,6 @@ bool MovieElement::readAttribute(MiniscriptThread *thread, DynamicValue &result,
}
bool MovieElement::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
if (attrib == "direct") {
writeProxy = DynamicValueWriteFuncHelper<MovieElement, &MovieElement::scriptSetDirect>::create(this);
return true;
}
if (attrib == "paused") {
writeProxy = DynamicValueWriteFuncHelper<MovieElement, &MovieElement::scriptSetPaused>::create(this);
@ -90,12 +96,82 @@ bool MovieElement::writeRefAttribute(MiniscriptThread *thread, DynamicValueWrite
return VisualElement::writeRefAttribute(thread, writeProxy, attrib);
}
bool MovieElement::scriptSetDirect(const DynamicValue &dest) {
if (dest.getType() == DynamicValueTypes::kBoolean) {
_directToScreen = dest.getBool();
return true;
VThreadState MovieElement::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
if (Event::create(EventIDs::kPlay, 0).respondsTo(msg->getEvent())) {
StartPlayingTaskData *startPlayingTaskData = runtime->getVThread().pushTask(this, &MovieElement::startPlayingTask);
startPlayingTaskData->runtime = runtime;
ChangeFlagTaskData *becomeVisibleTaskData = runtime->getVThread().pushTask(static_cast<VisualElement *>(this), &MovieElement::changeVisibilityTask);
becomeVisibleTaskData->desiredFlag = true;
becomeVisibleTaskData->runtime = runtime;
}
return kVThreadReturn;
}
void MovieElement::activate() {
Project *project = _runtime->getProject();
Common::SharedPtr<Asset> asset = project->getAssetByID(_assetID).lock();
if (!asset) {
warning("Movie element references asset %i but the asset isn't loaded!", _assetID);
return;
}
if (asset->getAssetType() != kAssetTypeMovie) {
warning("Movie element assigned an asset that isn't a movie");
return;
}
MovieAsset *movieAsset = static_cast<MovieAsset *>(asset.get());
size_t streamIndex = movieAsset->getStreamIndex();
int segmentIndex = project->getSegmentForStreamIndex(streamIndex);
project->openSegmentStream(segmentIndex);
Common::SeekableReadStream *stream = project->getStreamForSegment(segmentIndex);
if (!stream) {
warning("Movie element stream could not be opened");
return;
}
Video::QuickTimeDecoder *qtDecoder = new Video::QuickTimeDecoder();
qtDecoder->setChunkBeginOffset(movieAsset->getMovieDataPos());
_videoDecoder.reset(qtDecoder);
Common::SafeSeekableSubReadStream *movieDataStream = new Common::SafeSeekableSubReadStream(stream, movieAsset->getMovieDataPos(), movieAsset->getMovieDataPos() + movieAsset->getMovieDataSize(), DisposeAfterUse::NO);
if (!_videoDecoder->loadStream(movieDataStream))
_videoDecoder.reset();
_unloadSignaller = project->notifyOnSegmentUnload(segmentIndex, this);
}
void MovieElement::deactivate() {
if (_unloadSignaller) {
_unloadSignaller->removeReceiver(this);
_unloadSignaller.reset();
}
_videoDecoder.reset();
}
void MovieElement::render(Window *window) {
const Graphics::Surface *videoSurface = nullptr;
while (_videoDecoder->needsUpdate()) {
videoSurface = _videoDecoder->decodeNextFrame();
if (_playEveryFrame)
break;
}
if (videoSurface) {
Graphics::ManagedSurface *target = window->getSurface().get();
Common::Rect srcRect(0, 0, videoSurface->w, videoSurface->h);
Common::Rect destRect(_rect.left, _rect.top, _rect.right, _rect.bottom);
target->blitFrom(*videoSurface, srcRect, destRect);
}
return false;
}
bool MovieElement::scriptSetPaused(const DynamicValue& dest) {
@ -106,4 +182,26 @@ bool MovieElement::scriptSetPaused(const DynamicValue& dest) {
return false;
}
void MovieElement::onSegmentUnloaded(int segmentIndex) {
_videoDecoder.reset();
}
VThreadState MovieElement::startPlayingTask(const StartPlayingTaskData &taskData) {
if (_videoDecoder && !_videoDecoder->isPlaying()) {
EventIDs::EventID eventToSend = EventIDs::kPlay;
if (_paused) {
_paused = false;
eventToSend = EventIDs::kUnpause;
}
_videoDecoder->start();
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(eventToSend, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
taskData.runtime->sendMessageOnVThread(dispatch);
}
return kVThreadReturn;
}
} // End of namespace MTropolis

View File

@ -25,6 +25,12 @@
#include "mtropolis/data.h"
#include "mtropolis/runtime.h"
namespace Video {
class VideoDecoder;
} // End of namespace Video
namespace MTropolis {
struct ElementLoaderContext;
@ -36,16 +42,18 @@ public:
bool load(ElementLoaderContext &context, const Data::GraphicElement &data);
void render(Window *window) override;
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "Graphic Element"; }
SupportStatus debugGetSupportStatus() const override { return kSupportStatusPartial; }
#endif
private:
bool _directToScreen;
bool _cacheBitmap;
};
class MovieElement : public VisualElement {
class MovieElement : public VisualElement, public ISegmentUnloadSignalReceiver {
public:
MovieElement();
~MovieElement();
@ -54,22 +62,39 @@ public:
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib);
bool writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib);
VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
void activate() override;
void deactivate() override;
void render(Window *window) override;
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "Movie Element"; }
#endif
private:
bool scriptSetDirect(const DynamicValue &dest);
bool scriptSetPaused(const DynamicValue &dest);
bool _directToScreen;
void onSegmentUnloaded(int segmentIndex) override;
struct StartPlayingTaskData {
Runtime *runtime;
};
VThreadState startPlayingTask(const StartPlayingTaskData &taskData);
bool _cacheBitmap;
bool _paused;
bool _loop;
bool _alternate;
bool _playEveryFrame;
uint32 _assetID;
Common::SharedPtr<Video::VideoDecoder> _videoDecoder;
Common::SharedPtr<SegmentUnloadSignaller> _unloadSignaller;
Runtime *_runtime;
};
} // End of namespace MTropolis

View File

@ -241,8 +241,6 @@ void MTropolisEngine::handleEvents() {
Common::Error MTropolisEngine::run() {
int preferredWidth = 1024;
int preferredHeight = 768;
bool enableFrameRateLimit = true;
uint expectedFrameRate = 60;
ColorDepthMode preferredColorDepthMode = kColorDepthMode8Bit;
_runtime.reset(new Runtime(_system));
@ -417,8 +415,6 @@ Common::Error MTropolisEngine::run() {
bool paused = false;
int frameCounter = 0;
int numSkippedFrames = 0;
while (!shouldQuit()) {
handleEvents();

View File

@ -64,7 +64,7 @@ inline int quantize8To5(int value, byte orderedDither16x16) {
return (value * 249 + (orderedDither16x16 << 3)) >> 11;
}
Window::Window(int32 x, int32 y, int16 width, int16 height, const Graphics::PixelFormat &format) : _x(x), _y(y) {
Window::Window(Runtime *runtime, int32 x, int32 y, int16 width, int16 height, const Graphics::PixelFormat &format) : _runtime(runtime), _x(x), _y(y) {
_surface.reset(new Graphics::ManagedSurface(width, height, format));
}
@ -93,6 +93,18 @@ const Graphics::PixelFormat& Window::getPixelFormat() const {
return _surface->format;
}
void Window::close() {
Runtime *runtime = _runtime;
_runtime = nullptr;
if (runtime)
runtime->removeWindow(this);
}
void Window::detachFromRuntime() {
_runtime = nullptr;
}
namespace Render {
uint32 resolveRGB(uint8 r, uint8 g, uint8 b, const Graphics::PixelFormat &fmt) {
@ -104,6 +116,66 @@ uint32 resolveRGB(uint8 r, uint8 g, uint8 b, const Graphics::PixelFormat &fmt) {
return rPlaced | gPlaced | bPlaced | aPlaced;
}
static void recursiveCollectDrawElements(Structural *structural, Common::Array<VisualElement *> &normalBucket, Common::Array<VisualElement *> &directBucket) {
if (structural->isElement()) {
Element *element = static_cast<Element *>(structural);
if (element->isVisual()) {
VisualElement *visualElement = static_cast<VisualElement *>(element);
if (visualElement->isVisible()) {
if (visualElement->isDirectToScreen())
directBucket.push_back(visualElement);
else
normalBucket.push_back(visualElement);
}
}
}
for (Common::Array<Common::SharedPtr<Structural> >::const_iterator it = structural->getChildren().begin(), itEnd = structural->getChildren().end(); it != itEnd; ++it) {
recursiveCollectDrawElements(it->get(), normalBucket, directBucket);
}
}
static bool visualElementLayerLess(VisualElement *a, VisualElement *b) {
return a->getLayer() < b->getLayer();
}
static void renderNormalElement(VisualElement *element, Window *mainWindow) {
element->render(mainWindow);
}
static void renderDirectElement(VisualElement *element, Window *mainWindow) {
renderNormalElement(element, mainWindow); // Meh
}
void renderProject(Runtime *runtime, Window *mainWindow) {
Common::Array<Structural *> scenes;
runtime->getScenesInRenderOrder(scenes);
Common::Array<VisualElement *> normalBucket;
Common::Array<VisualElement *> directBucket;
for (Common::Array<Structural *>::const_iterator it = scenes.begin(), itEnd = scenes.end(); it != itEnd; ++it) {
size_t normalStart = normalBucket.size();
size_t directStart = directBucket.size();
recursiveCollectDrawElements(*it, normalBucket, directBucket);
size_t normalEnd = normalBucket.size();
size_t directEnd = directBucket.size();
if (normalEnd - normalStart > 1)
Common::sort(normalBucket.begin() + normalStart, normalBucket.end(), visualElementLayerLess);
if (directEnd - directStart > 1)
Common::sort(directBucket.begin() + directStart, directBucket.end(), visualElementLayerLess);
}
for (Common::Array<VisualElement *>::const_iterator it = normalBucket.begin(), itEnd = normalBucket.end(); it != itEnd; ++it)
renderNormalElement(*it, mainWindow);
for (Common::Array<VisualElement *>::const_iterator it = directBucket.begin(), itEnd = directBucket.end(); it != itEnd; ++it)
renderDirectElement(*it, mainWindow);
}
} // End of namespace Render
} // End of namespace MTropolis

View File

@ -35,9 +35,12 @@ class ManagedSurface;
namespace MTropolis {
class Runtime;
class Project;
class Window {
public:
Window(int32 x, int32 y, int16 width, int16 height, const Graphics::PixelFormat &format);
Window(Runtime *runtime, int32 x, int32 y, int16 width, int16 height, const Graphics::PixelFormat &format);
~Window();
int32 getX() const;
@ -47,15 +50,20 @@ public:
const Common::SharedPtr<Graphics::ManagedSurface> &getSurface() const;
const Graphics::PixelFormat &getPixelFormat() const;
void close();
void detachFromRuntime();
private:
int32 _x;
int32 _y;
Common::SharedPtr<Graphics::ManagedSurface> _surface;
Runtime *_runtime;
};
namespace Render {
uint32 resolveRGB(uint8 r, uint8 g, uint8 b, const Graphics::PixelFormat &fmt);
void renderProject(Runtime *runtime, Window *mainWindow);
} // End of namespace Render

View File

@ -95,6 +95,20 @@ void ModifierChildMaterializer::visitChildStructuralRef(Common::SharedPtr<Struct
}
void ModifierChildMaterializer::visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) {
if (modifier->isAlias()) {
Common::SharedPtr<Modifier> templateModifier = _runtime->getProject()->resolveAlias(static_cast<AliasModifier *>(modifier.get())->getAliasID());
if (!templateModifier) {
error("Failed to resolve alias");
}
if (!modifier->isVariable()) {
Common::SharedPtr<Modifier> clonedModifier = templateModifier->shallowClone();
clonedModifier->setName(modifier->getName());
modifier = clonedModifier;
}
}
modifier->materialize(_runtime, _outerScope);
}
@ -1708,12 +1722,18 @@ VThreadState Structural::consumeCommand(Runtime *runtime, const Common::SharedPt
return kVThreadReturn;
}
void Structural::activate() {
}
void Structural::deactivate() {
}
#ifdef MTROPOLIS_DEBUG_ENABLE
SupportStatus Structural::debugGetSupportStatus() const {
return kSupportStatusNone;
}
const Common::String& Structural::debugGetName() const {
const Common::String &Structural::debugGetName() const {
return _name;
}
@ -2121,11 +2141,13 @@ bool Runtime::runFrame() {
_macFontMan.reset(new Graphics::MacFontManager(0, desc->getLanguage()));
_project.reset(new Project());
_project.reset(new Project(this));
_project->setSelfReference(_project);
_project->loadFromDescription(*desc);
ensureMainWindowExists();
_rootLinkingScope.addObject(_project->getStaticGUID(), _project->getName(), _project);
// We have to materialize global variables because they are not cloned from aliases.
@ -2229,6 +2251,12 @@ void Runtime::drawFrame() {
_system->fillScreen(Render::resolveRGB(0, 0, 0, getRenderPixelFormat()));
{
Common::SharedPtr<Window> mainWindow = _mainWindow.lock();
if (mainWindow)
Render::renderProject(this, mainWindow.get());
}
for (Common::Array<Common::SharedPtr<Window> >::const_iterator it = _windows.begin(), itEnd = _windows.end(); it != itEnd; ++it) {
const Window &window = *it->get();
const Graphics::ManagedSurface &surface = *window.getSurface();
@ -2292,6 +2320,8 @@ void Runtime::executeTeardown(const Teardown &teardown) {
if (!structural)
return; // Already gone
recursiveDeactivateStructural(structural.get());
if (teardown.onlyRemoveChildren) {
structural->removeAllChildren();
structural->removeAllModifiers();
@ -2533,6 +2563,24 @@ void Runtime::executeSharedScenePostSceneChangeActions() {
}
}
void Runtime::recursiveDeactivateStructural(Structural *structural) {
const Common::Array<Common::SharedPtr<Structural> > &children = structural->getChildren();
for (Common::Array<Common::SharedPtr<Structural> >::const_iterator it = children.begin(), itEnd = children.end(); it != itEnd; ++it) {
recursiveDeactivateStructural(it->get());
}
structural->deactivate();
}
void Runtime::recursiveActivateStructural(Structural *structural) {
structural->activate();
const Common::Array<Common::SharedPtr<Structural> > &children = structural->getChildren();
for (Common::Array<Common::SharedPtr<Structural> >::const_iterator it = children.begin(), itEnd = children.end(); it != itEnd; ++it) {
recursiveActivateStructural(it->get());
}
}
void Runtime::queueEventAsLowLevelSceneStateTransitionAction(const Event &evt, Structural *root, bool cascade, bool relay) {
Common::SharedPtr<MessageProperties> props(new MessageProperties(evt, DynamicValue(), Common::WeakPtr<RuntimeObject>()));
Common::SharedPtr<MessageDispatch> msg(new MessageDispatch(props, root, cascade, relay, false));
@ -2550,6 +2598,8 @@ void Runtime::loadScene(const Common::SharedPtr<Structural>& scene) {
debug(1, "Scene loaded OK, materializing objects...");
scene->materializeDescendents(this, subsection->getSceneLoadMaterializeScope());
debug(1, "Scene materialized OK");
recursiveActivateStructural(scene.get());
debug(1, "Scene added to renderer OK");
#ifdef MTROPOLIS_DEBUG_ENABLE
if (_debugger) {
@ -2599,6 +2649,12 @@ Scheduler &Runtime::getScheduler() {
return _scheduler;
}
void Runtime::getScenesInRenderOrder(Common::Array<Structural*> &scenes) const {
for (Common::Array<SceneStackEntry>::const_iterator it = _sceneStack.begin(), itEnd = _sceneStack.end(); it != itEnd; ++it) {
scenes.push_back(it->scene.get());
}
}
void Runtime::ensureMainWindowExists() {
// Maybe there's a better spot for this
@ -2607,7 +2663,7 @@ void Runtime::ensureMainWindowExists() {
int32 centeredX = (static_cast<int32>(_displayWidth) - static_cast<int32>(presentationSettings.width)) / 2;
int32 centeredY = (static_cast<int32>(_displayHeight) - static_cast<int32>(presentationSettings.height)) / 2;
Common::SharedPtr<Window> mainWindow(new Window(centeredX, centeredY, presentationSettings.width, presentationSettings.height, _displayModePixelFormats[_realDisplayMode]));
Common::SharedPtr<Window> mainWindow(new Window(this, centeredX, centeredY, presentationSettings.width, presentationSettings.height, _displayModePixelFormats[_realDisplayMode]));
addWindow(mainWindow);
_mainWindow.reset(mainWindow);
}
@ -2682,6 +2738,7 @@ void Runtime::addWindow(const Common::SharedPtr<Window> &window) {
void Runtime::removeWindow(Window *window) {
for (size_t i = 0; i < _windows.size(); i++) {
if (_windows[i].get() == window) {
window->detachFromRuntime();
_windows.remove_at(i);
break;
}
@ -2806,11 +2863,46 @@ const IPlugInModifierFactory *ProjectPlugInRegistry::findPlugInModifierFactory(c
return it->_value;
}
Project::Project()
: _projectFormat(Data::kProjectFormatUnknown), _isBigEndian(false), _haveGlobalObjectInfo(false), _haveProjectStructuralDef(false) {
SegmentUnloadSignaller::SegmentUnloadSignaller(Project *project, int segmentIndex) : _project(project), _segmentIndex(segmentIndex) {
}
SegmentUnloadSignaller::~SegmentUnloadSignaller() {
}
void SegmentUnloadSignaller::onSegmentUnloaded() {
_project = nullptr;
// Need to be careful here because a receiver may unload this object, causing _receivers.size() to be invalid
const size_t numReceivers = _receivers.size();
for (size_t i = 0; i < numReceivers; i++) {
_receivers[i]->onSegmentUnloaded(_segmentIndex);
}
}
void SegmentUnloadSignaller::addReceiver(ISegmentUnloadSignalReceiver *receiver) {
_receivers.push_back(receiver);
}
void SegmentUnloadSignaller::removeReceiver(ISegmentUnloadSignalReceiver *receiver) {
for (size_t i = 0; i < _receivers.size(); i++) {
if (_receivers[i] == receiver) {
_receivers.remove_at(i);
break;
}
}
}
Project::Segment::Segment() : weakStream(nullptr) {
}
Project::Project(Runtime *runtime)
: _runtime(runtime), _projectFormat(Data::kProjectFormatUnknown), _isBigEndian(false), _haveGlobalObjectInfo(false), _haveProjectStructuralDef(false) {
}
Project::~Project() {
for (size_t i = 0; i < _segments.size(); i++)
closeSegmentStream(i);
}
void Project::loadFromDescription(const ProjectDescription& desc) {
@ -2927,7 +3019,9 @@ void Project::loadSceneFromStream(const Common::SharedPtr<Structural>& scene, ui
error("Invalid stream ID");
}
const StreamDesc &streamDesc = _streams[streamID - 1];
size_t streamIndex = streamID - 1;
const StreamDesc &streamDesc = _streams[streamIndex];
uint segmentIndex = streamDesc.segmentIndex;
openSegmentStream(segmentIndex);
@ -2972,12 +3066,12 @@ void Project::loadSceneFromStream(const Common::SharedPtr<Structural>& scene, ui
if (Data::DataObjectTypes::isAsset(dataObjectType)) {
// Asset defs can appear anywhere
loadAssetDef(assetDefLoader, *dataObject.get());
loadAssetDef(streamIndex, assetDefLoader, *dataObject.get());
} else if (dataObjectType == Data::DataObjectTypes::kAssetDataChunk) {
// Ignore
continue;
} else if (loaderStack.contexts.size() > 0) {
loadContextualObject(loaderStack, *dataObject.get());
loadContextualObject(streamIndex, loaderStack, *dataObject.get());
} else {
error("Unexpectedly exited scene context in loader");
}
@ -2990,6 +3084,7 @@ void Project::loadSceneFromStream(const Common::SharedPtr<Structural>& scene, ui
}
scene->holdAssets(assetDefLoader.assets);
assignAssets(assetDefLoader.assets);
}
Common::SharedPtr<Modifier> Project::resolveAlias(uint32 aliasID) const {
@ -3018,6 +3113,21 @@ bool Project::isProject() const {
return true;
}
Common::WeakPtr<Asset> Project::getAssetByID(uint32 assetID) const {
if (assetID >= _assetsByID.size())
return Common::WeakPtr<Asset>();
const AssetDesc *desc = _assetsByID[assetID];
if (desc == nullptr)
return Common::WeakPtr<Asset>();
return desc->asset;
}
size_t Project::getSegmentForStreamIndex(size_t streamIndex) const {
return _streams[streamIndex].segmentIndex;
}
void Project::openSegmentStream(int segmentIndex) {
if (segmentIndex < 0 || static_cast<size_t>(segmentIndex) > _segments.size()) {
error("Invalid segment index %i", segmentIndex);
@ -3040,6 +3150,31 @@ void Project::openSegmentStream(int segmentIndex) {
error("Failed to open segment file %s", segment.desc.filePath.c_str());
}
}
segment.unloadSignaller.reset(new SegmentUnloadSignaller(this, segmentIndex));
}
void Project::closeSegmentStream(int segmentIndex) {
Segment &segment = _segments[segmentIndex];
if (!segment.weakStream)
return;
segment.unloadSignaller->onSegmentUnloaded();
segment.unloadSignaller.reset();
segment.rcStream.reset();
segment.weakStream = nullptr;
}
Common::SeekableReadStream* Project::getStreamForSegment(int segmentIndex) {
return _segments[segmentIndex].weakStream;
}
Common::SharedPtr<SegmentUnloadSignaller> Project::notifyOnSegmentUnload(int segmentIndex, ISegmentUnloadSignalReceiver *receiver) {
Common::SharedPtr<SegmentUnloadSignaller> signaller = _segments[segmentIndex].unloadSignaller;
if (signaller)
signaller->addReceiver(receiver);
return signaller;
}
void Project::loadBootStream(size_t streamIndex) {
@ -3069,12 +3204,12 @@ void Project::loadBootStream(size_t streamIndex) {
if (Data::DataObjectTypes::isAsset(dataObjectType)) {
// Asset defs can appear anywhere
loadAssetDef(assetDefLoader, *dataObject.get());
loadAssetDef(streamIndex, assetDefLoader, *dataObject.get());
} else if (dataObjectType == Data::DataObjectTypes::kAssetDataChunk) {
// Ignore
continue;
} else if (loaderStack.contexts.size() > 0) {
loadContextualObject(loaderStack, *dataObject.get());
loadContextualObject(streamIndex, loaderStack, *dataObject.get());
} else {
// Root-level objects
switch (dataObject->getType()) {
@ -3120,6 +3255,7 @@ void Project::loadBootStream(size_t streamIndex) {
}
holdAssets(assetDefLoader.assets);
assignAssets(assetDefLoader.assets);
}
void Project::loadPresentationSettings(const Data::PresentationSettings &presentationSettings) {
@ -3167,7 +3303,7 @@ void Project::loadAssetCatalog(const Data::AssetCatalog &assetCatalog) {
}
}
void Project::loadGlobalObjectInfo(ChildLoaderStack& loaderStack, const Data::GlobalObjectInfo& globalObjectInfo) {
void Project::loadGlobalObjectInfo(ChildLoaderStack &loaderStack, const Data::GlobalObjectInfo& globalObjectInfo) {
if (_haveGlobalObjectInfo)
error("Multiple global object infos");
@ -3280,7 +3416,28 @@ ObjectLinkingScope *Project::getPersistentModifierScope() {
return &_modifierScope;
}
void Project::loadContextualObject(ChildLoaderStack &stack, const Data::DataObject &dataObject) {
void Project::assignAssets(const Common::Array<Common::SharedPtr<Asset> >& assets) {
for (Common::Array<Common::SharedPtr<Asset> >::const_iterator it = assets.begin(), itEnd = assets.end(); it != itEnd; ++it) {
Common::SharedPtr<Asset> asset = *it;
uint32 assetID = asset->getAssetID();
if (assetID >= _assetsByID.size()) {
warning("Bad asset ID %u", assetID);
continue;
}
AssetDesc *desc = _assetsByID[assetID];
if (desc == nullptr) {
warning("Asset attempting to use deleted asset slot %u", assetID);
continue;
}
if (desc->asset.expired())
desc->asset = asset;
}
}
void Project::loadContextualObject(size_t streamIndex, ChildLoaderStack &stack, const Data::DataObject &dataObject) {
ChildLoaderContext &topContext = stack.contexts.back();
const Data::DataObjectTypes::DataObjectType dataObjectType = dataObject.getType();
@ -3408,7 +3565,7 @@ void Project::loadContextualObject(ChildLoaderStack &stack, const Data::DataObje
error("No element factory defined for structural object");
}
ElementLoaderContext elementLoaderContext;
ElementLoaderContext elementLoaderContext(_runtime, streamIndex);
Common::SharedPtr<Element> element = elementFactory->createElement(elementLoaderContext, dataObject);
container->addChild(element);
@ -3448,7 +3605,7 @@ void Project::loadContextualObject(ChildLoaderStack &stack, const Data::DataObje
}
}
void Project::loadAssetDef(AssetDefLoaderContext& context, const Data::DataObject& dataObject) {
void Project::loadAssetDef(size_t streamIndex, AssetDefLoaderContext& context, const Data::DataObject& dataObject) {
assert(Data::DataObjectTypes::isAsset(dataObject.getType()));
IAssetFactory *factory = getAssetFactoryForDataObjectType(dataObject.getType());
@ -3457,7 +3614,7 @@ void Project::loadAssetDef(AssetDefLoaderContext& context, const Data::DataObjec
return;
}
AssetLoaderContext loaderContext;
AssetLoaderContext loaderContext(streamIndex);
context.assets.push_back(factory->createAsset(loaderContext, dataObject));
}
@ -3519,11 +3676,23 @@ bool VisualElement::isVisible() const {
return _visible;
}
bool VisualElement::isDirectToScreen() const {
return _directToScreen;
}
uint16 VisualElement::getLayer() const {
return _layer;
}
bool VisualElement::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
if (attrib == "visible") {
result.setBool(_visible);
return true;
}
if (attrib == "direct") {
result.setBool(_directToScreen);
return true;
}
return Element::readAttribute(thread, result, attrib);
}
@ -3533,11 +3702,16 @@ bool VisualElement::writeRefAttribute(MiniscriptThread *thread, DynamicValueWrit
writeProxy = DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetVisibility>::create(this);
return true;
}
if (attrib == "direct") {
writeProxy = DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetDirect>::create(this);
return true;
}
return Element::writeRefAttribute(thread, writeProxy, attrib);
}
bool VisualElement::scriptSetVisibility(const DynamicValue& result) {
// FIXME: Need to make this fire Show/Hide events!
if (result.getType() == DynamicValueTypes::kBoolean) {
_visible = result.getBool();
return true;
@ -3553,6 +3727,7 @@ bool VisualElement::loadCommon(const Common::String &name, uint32 guid, const Da
_name = name;
_guid = guid;
_visible = ((elementFlags & Data::ElementFlags::kHidden) == 0);
_directToScreen = ((elementFlags & Data::ElementFlags::kNotDirectToScreen) == 0);
_streamLocator = streamLocator;
_sectionID = sectionID;
_layer = layer;
@ -3560,6 +3735,26 @@ bool VisualElement::loadCommon(const Common::String &name, uint32 guid, const Da
return true;
}
bool VisualElement::scriptSetDirect(const DynamicValue &dest) {
if (dest.getType() == DynamicValueTypes::kBoolean) {
_directToScreen = dest.getBool();
return true;
}
return false;
}
VThreadState VisualElement::changeVisibilityTask(const ChangeFlagTaskData &taskData) {
if (_visible != taskData.desiredFlag) {
_visible = taskData.desiredFlag;
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(_visible ? EventIDs::kElementHide : EventIDs::kElementShow, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
taskData.runtime->sendMessageOnVThread(dispatch);
}
return kVThreadReturn;
}
bool NonVisualElement::isVisual() const {
return false;
}

View File

@ -1060,6 +1060,8 @@ public:
Scheduler &getScheduler();
void getScenesInRenderOrder(Common::Array<Structural *> &scenes) const;
#ifdef MTROPOLIS_DEBUG_ENABLE
void debugSetEnabled(bool enabled);
void debugBreak();
@ -1111,6 +1113,9 @@ private:
void executeCompleteTransitionToScene(const Common::SharedPtr<Structural> &scene);
void executeSharedScenePostSceneChangeActions();
void recursiveDeactivateStructural(Structural *structural);
void recursiveActivateStructural(Structural *structural);
void queueEventAsLowLevelSceneStateTransitionAction(const Event &evt, Structural *root, bool cascade, bool relay);
void loadScene(const Common::SharedPtr<Structural> &scene);
@ -1281,6 +1286,9 @@ public:
virtual VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg);
virtual void activate();
virtual void deactivate();
#ifdef MTROPOLIS_DEBUG_ENABLE
SupportStatus debugGetSupportStatus() const override;
const Common::String &debugGetName() const override;
@ -1368,10 +1376,28 @@ private:
Common::HashMap<Common::String, const IPlugInModifierFactory *> _factoryRegistry;
};
class Project : public Structural {
struct ISegmentUnloadSignalReceiver {
virtual void onSegmentUnloaded(int segmentIndex) = 0;
};
class SegmentUnloadSignaller {
public:
Project();
explicit SegmentUnloadSignaller(Project *project, int segmentIndex);
~SegmentUnloadSignaller();
void onSegmentUnloaded();
void addReceiver(ISegmentUnloadSignalReceiver *receiver);
void removeReceiver(ISegmentUnloadSignalReceiver *receiver);
private:
Project *_project;
int _segmentIndex;
Common::Array<ISegmentUnloadSignalReceiver *> _receivers;
};
class Project : public Structural {
public:
explicit Project(Runtime *runtime);
~Project();
void loadFromDescription(const ProjectDescription &desc);
@ -1384,6 +1410,13 @@ public:
bool isProject() const override;
Common::WeakPtr<Asset> getAssetByID(uint32 assetID) const;
size_t getSegmentForStreamIndex(size_t streamIndex) const;
void openSegmentStream(int segmentIndex);
void closeSegmentStream(int segmentIndex);
Common::SeekableReadStream *getStreamForSegment(int segmentIndex);
Common::SharedPtr<SegmentUnloadSignaller> notifyOnSegmentUnload(int segmentIndex, ISegmentUnloadSignalReceiver *receiver);
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "Project"; }
#endif
@ -1407,9 +1440,12 @@ private:
};
struct Segment {
Segment();
SegmentDescription desc;
Common::SharedPtr<Common::SeekableReadStream> rcStream;
Common::SeekableReadStream *weakStream;
Common::SharedPtr<SegmentUnloadSignaller> unloadSignaller;
};
enum StreamType {
@ -1436,14 +1472,13 @@ private:
Common::WeakPtr<Asset> asset;
};
void openSegmentStream(int segmentIndex);
void loadBootStream(size_t streamIndex);
void loadPresentationSettings(const Data::PresentationSettings &presentationSettings);
void loadAssetCatalog(const Data::AssetCatalog &assetCatalog);
void loadGlobalObjectInfo(ChildLoaderStack &loaderStack, const Data::GlobalObjectInfo &globalObjectInfo);
void loadAssetDef(AssetDefLoaderContext &context, const Data::DataObject &dataObject);
void loadContextualObject(ChildLoaderStack &stack, const Data::DataObject &dataObject);
void loadAssetDef(size_t streamIndex, AssetDefLoaderContext &context, const Data::DataObject &dataObject);
void loadContextualObject(size_t streamIndex, ChildLoaderStack &stack, const Data::DataObject &dataObject);
Common::SharedPtr<Modifier> loadModifierObject(ModifierLoaderContext &loaderContext, const Data::DataObject &dataObject);
void loadLabelMap(const Data::ProjectLabelMap &projectLabelMap);
static size_t recursiveCountLabels(const Data::ProjectLabelMap::LabelTree &tree);
@ -1451,6 +1486,8 @@ private:
ObjectLinkingScope *getPersistentStructuralScope() override;
ObjectLinkingScope *getPersistentModifierScope() override;
void assignAssets(const Common::Array<Common::SharedPtr<Asset> > &assets);
Common::Array<Segment> _segments;
Common::Array<StreamDesc> _streams;
Common::Array<LabelTree> _labelTree;
@ -1475,6 +1512,8 @@ private:
Common::SharedPtr<ProjectResources> _resources;
ObjectLinkingScope _structuralScope;
ObjectLinkingScope _modifierScope;
Runtime *_runtime;
};
class Section : public Structural {
@ -1532,15 +1571,29 @@ public:
bool isVisual() const override;
bool isVisible() const;
bool isDirectToScreen() const;
uint16 getLayer() const;
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib);
bool writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib);
bool scriptSetVisibility(const DynamicValue &result);
virtual void render(Window *window) = 0;
protected:
bool loadCommon(const Common::String &name, uint32 guid, const Data::Rect &rect, uint32 elementFlags, uint16 layer, uint32 streamLocator, uint16 sectionID);
bool scriptSetDirect(const DynamicValue &dest);
struct ChangeFlagTaskData {
bool desiredFlag;
Runtime *runtime;
};
VThreadState changeVisibilityTask(const ChangeFlagTaskData &taskData);
bool _directToScreen;
bool _visible;
Rect16 _rect;
uint16 _layer;
@ -1630,11 +1683,23 @@ public:
virtual void getValue(DynamicValue &dest) const = 0;
};
enum AssetType {
kAssetTypeNone,
kAssetTypeMovie,
kAssetTypeAudio,
kAssetTypeColorTable,
};
class Asset {
public:
Asset();
virtual ~Asset();
uint32 getAssetID() const;
virtual AssetType getAssetType() const = 0;
protected:
uint32 _assetID;
};