Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Adminotech Tundra fixes #480

Merged
merged 14 commits into from

2 participants

@jonnenauha
Owner
  • Call AssetAPI::ForgetAllAssets() when client disconnects from a server. This needs to be done if we want Tundra to be able to login to multiple scenes in one instance/run. If this is not done, eventually client will run out of memory and crash. Fixes #472
  • Fix linking incorrectly to RenderSystem_Direct3D9 if CMakeLists.txt ENABLE_DIRECTX == false.
  • Protect against null service objects (framework property maps QObjects) while prepping JS engine.
  • Implement a Abort() mechanism for IAssetTransfer. This flag cannot be set from scripts, AssetAPI sets aborted when appropriate. This lets the provider know that once the load finishes (in http providers case for example) to not load anything into memory or store the cache file. Additionally AssetAPI can detect aborted flag and ignore the finished/failed transfer. Before it incorrectly did error logging for the transfer we didn't even expect anymore and constructed from its data an IAsset into memory.
  • Make the load path for AssetAPI uniform how AssetLoadFailed is called. Before there were situations where disk source had "a" path but the file was no present. In this scenario AssetAPI was never informed that the load actually failed which left dead transfers into the internal state. There might have been other obscure asset load paths that resulted in the same bug.
  • Fix crash that occurred if Ogre IAsset was loading the data asynchronously in Ogre and it was destroyed/unloaded. This left a dangling IAsset* into Ogre that it called back when the asynch load completed. Now if a asynch operation is ongoing the request is aborted in IAsset::DoUnload().
  • Try to recover from UI blit rects that have areas outside the scene rect. Add more information to error logging about the rects in play when DX surface subrect blit fails.
  • Enable CanvasPlugin into the default Tundra build and startup configs.
  • Fixed ~EC_RttTarget crash bug in headless server. Underlying bug reported in #481
jonnenauha and others added some commits
@jonnenauha jonnenauha AssetAPI: Fix bug where failed asset loads left their asset transfers…
… into the transfer map incorrectly indefinately.
444aed7
@jonnenauha jonnenauha JavascriptModule: Prevent null crashes on framework property QObjects…
… if the ptr has been freed, not a active bug bug a lurking one if some 3rd party module misuses the dyn obj registration to framework. On a side note: We should add a remove counterpart to RegisterDynamicObject(...).
53e318d
@jonnenauha jonnenauha AssetAPI/AssetModule: Implement a Abort() mechanism to IAssetTransfer…
…. For now the abort is only used from AssetAPI itself in ForgetAllAssets() (when it also clears the shared_ptr transfer map, meaning its no longer expecting it), it cannot be called from scripts. AssetAPI will just ignore Aborted() transfer when they finish and make the appropriate callback to AssetAPI and not load anything into mem. Before the Abort() mechanism any 'forgotten' transfers finishing, meant we logged a warninig but still loaded the asset fully to memory (yes, to ogre aswell). This should never happen if AssetAPI is told to forget all assets and ongoing transfers.
e76f270
@jonnenauha jonnenauha Client: After removing the client scene when disconnecting from a ser…
…ver, forget all assets in AssetAPI. This prepares Tundra to be more suitable for multiple scene logins on one Tundra instance. If we will not do this, all the visited scenes assets will stay in memory and after a while you will not be able to login to any scene without running into our of memory issues.
229d413
@jonnenauha jonnenauha Rendere: Add more useful information on rendering errors where sub re…
…ct of the DX surface could not be locked. Add code that will correct if the constructed blit subrect is (partly) outside our scene rect.
256ace7
@jonnenauha jonnenauha Threaded ogre asset crash fixes: If the asynch load operation is ongo…
…ing in ogre make sure to abort it when unloading the IAsset (so also for dtor/freeing the asset). If this is not done, ogre will have a dangling callback ptr to our IAsset and will crash once the threaded asset load is completed. Now its safe to forget/unload/free ogre assets that are currently being loaded asynchronously.
4cb658a
@jonnenauha jonnenauha Add CanvasPlugin to the default build. 587cc43
Cvetan Stefanovski AssetAPI: Use interator instead of const_iterator - GCC does not allo…
…w erase() with const_iterator as argument
509ef83
@jonnenauha jonnenauha FindOgre.cmake: Fix that we do not link against DX render plugin if D…
…irectX is not enabled in the CMakeLists.txt:s defined.
eaace11
@jonnenauha jonnenauha Add CanvasPlugin to viewer and default server startup configs. 2da8db8
@jonnenauha jonnenauha EC_RttTarget: Crashes server on dtor where asks ViewEnabled() if ogre…
… stuff should be unloaded. The bug is actually in ViewEnabled() as it returns true if parent entity is null (which it is in component dtor). fw->IsHeadless() is safe and is essentially same thing. Making a issue about the weird behaviour of ViewEnabled() in dtor.
0c3c964
@juj
Owner

Is it possible to implement the asset transfer abort mechanism without using a 'bool aborted;"? If Abort() is called on a transfer, can we just cancel the transfer right there and then, notify the provider about the abort request and have it handle it.

That would be a stateless design, and would cause the transfer itself to abort, so that abort wouldn't mean "finish transfer and discard.".

@jonnenauha
Owner

We'll the transfer itself is not "doing" the data trasnfer, its just waiting for the result from the provider. So its waiting in an aborted state. I guess we could replace the boolean with a signal that providers listen to and remove from their internal maps so the transfer would never go to AssetAPI::LoadFailed/Complete. Is this what you are looking for? There might just be some code paths that print errors if the completed transfer is not found in whatever internal bookkeeping the provider has. Just like assetapi had, before it knew they were aborted.

But do we really want another signal either to IAssetTransfer? I'll give it a look at work later.

@juj
Owner

Using an immediate path for halting the transfer sounds nicer than letting the transfer to finish to completion and using a boolean to track and kill it.

This does not have to be a signal, but can also be implemented via a virtual IAssetTransfer::Abort() call, which can call IAssetTransfer::provider->AbortTransfer(this); to route the abort handling to its own IAssetProvider object.

jonnenauha added some commits
@jonnenauha jonnenauha AssetAPI: Refactor IAssetTransfer::Abort() to be stateless. Now by de…
…fault (if not overrided) it will calls its providers IAssetProvider::AbortTrasfer(). This is implemented now in both http and local providers. It will remove the transfer from the internal state (http also aborts the QNetorkReply). This way no caching or further downloading is done. Additionally automatically emits IAssetTransfer::Failed('Transfer aborted');.
f103d31
@jonnenauha jonnenauha Typo fixes to prints and docs. ad4e18f
@jonnenauha
Owner

There we go, if you'd have time to review that before the next rex release id appreciate it. Thanks.

@juj juj merged commit fc30f57 into from
@juj
Owner

Looks good and tested to function well.

This functionality is not necessarily the strictly preferred behavior for all use cases and with the initial implementation we remained indifferent about the policy to use. This implementation is structured well to allow a configuration to select the policy on disconnect (keep assets in memory vs clear all assets from memory), that can be implemented in the future if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 24, 2012
  1. @jonnenauha

    AssetAPI: Fix bug where failed asset loads left their asset transfers…

    jonnenauha authored
    … into the transfer map incorrectly indefinately.
  2. @jonnenauha

    JavascriptModule: Prevent null crashes on framework property QObjects…

    jonnenauha authored
    … if the ptr has been freed, not a active bug bug a lurking one if some 3rd party module misuses the dyn obj registration to framework. On a side note: We should add a remove counterpart to RegisterDynamicObject(...).
  3. @jonnenauha

    AssetAPI/AssetModule: Implement a Abort() mechanism to IAssetTransfer…

    jonnenauha authored
    …. For now the abort is only used from AssetAPI itself in ForgetAllAssets() (when it also clears the shared_ptr transfer map, meaning its no longer expecting it), it cannot be called from scripts. AssetAPI will just ignore Aborted() transfer when they finish and make the appropriate callback to AssetAPI and not load anything into mem. Before the Abort() mechanism any 'forgotten' transfers finishing, meant we logged a warninig but still loaded the asset fully to memory (yes, to ogre aswell). This should never happen if AssetAPI is told to forget all assets and ongoing transfers.
  4. @jonnenauha

    Client: After removing the client scene when disconnecting from a ser…

    jonnenauha authored
    …ver, forget all assets in AssetAPI. This prepares Tundra to be more suitable for multiple scene logins on one Tundra instance. If we will not do this, all the visited scenes assets will stay in memory and after a while you will not be able to login to any scene without running into our of memory issues.
  5. @jonnenauha

    Rendere: Add more useful information on rendering errors where sub re…

    jonnenauha authored
    …ct of the DX surface could not be locked. Add code that will correct if the constructed blit subrect is (partly) outside our scene rect.
  6. @jonnenauha

    Threaded ogre asset crash fixes: If the asynch load operation is ongo…

    jonnenauha authored
    …ing in ogre make sure to abort it when unloading the IAsset (so also for dtor/freeing the asset). If this is not done, ogre will have a dangling callback ptr to our IAsset and will crash once the threaded asset load is completed. Now its safe to forget/unload/free ogre assets that are currently being loaded asynchronously.
  7. @jonnenauha
  8. @jonnenauha

    AssetAPI: Use interator instead of const_iterator - GCC does not allo…

    Cvetan Stefanovski authored jonnenauha committed
    …w erase() with const_iterator as argument
  9. @jonnenauha

    FindOgre.cmake: Fix that we do not link against DX render plugin if D…

    jonnenauha authored
    …irectX is not enabled in the CMakeLists.txt:s defined.
  10. @jonnenauha
  11. @jonnenauha

    EC_RttTarget: Crashes server on dtor where asks ViewEnabled() if ogre…

    jonnenauha authored
    … stuff should be unloaded. The bug is actually in ViewEnabled() as it returns true if parent entity is null (which it is in component dtor). fw->IsHeadless() is safe and is essentially same thing. Making a issue about the weird behaviour of ViewEnabled() in dtor.
Commits on May 25, 2012
  1. @jonnenauha

    AssetAPI: Refactor IAssetTransfer::Abort() to be stateless. Now by de…

    jonnenauha authored
    …fault (if not overrided) it will calls its providers IAssetProvider::AbortTrasfer(). This is implemented now in both http and local providers. It will remove the transfer from the internal state (http also aborts the QNetorkReply). This way no caching or further downloading is done. Additionally automatically emits IAssetTransfer::Failed('Transfer aborted');.
  2. @jonnenauha
  3. @jonnenauha
This page is out of date. Refresh to see the latest.
Showing with 300 additions and 107 deletions.
  1. +1 −0  CMakeBuildConfigTemplate.txt
  2. +1 −0  bin/plugins.xml
  3. +2 −1  bin/viewer-browser.xml
  4. +2 −1  bin/viewer.xml
  5. +8 −3 cmake/FindOgre.cmake
  6. +4 −3 src/Application/JavascriptModule/JavascriptInstance.cpp
  7. +2 −1  src/Application/JavascriptModule/JavascriptInstance.h
  8. +10 −9 src/Application/JavascriptModule/JavascriptModule.cpp
  9. +24 −31 src/Core/Asset/AssetAPI.cpp
  10. +8 −6 src/Core/Asset/IAsset.cpp
  11. +9 −3 src/Core/Asset/IAssetProvider.h
  12. +70 −0 src/Core/Asset/IAssetTransfer.cpp
  13. +46 −31 src/Core/Asset/IAssetTransfer.h
  14. +24 −3 src/Core/AssetModule/HttpAssetProvider.cpp
  15. +4 −0 src/Core/AssetModule/HttpAssetProvider.h
  16. +1 −1  src/Core/AssetModule/HttpAssetTransfer.h
  17. +19 −1 src/Core/AssetModule/LocalAssetProvider.cpp
  18. +5 −1 src/Core/AssetModule/LocalAssetProvider.h
  19. +3 −1 src/Core/OgreRenderingModule/EC_RttTarget.cpp
  20. +12 −4 src/Core/OgreRenderingModule/OgreMeshAsset.cpp
  21. +2 −2 src/Core/OgreRenderingModule/OgreMeshAsset.h
  22. +11 −0 src/Core/OgreRenderingModule/OgreSkeletonAsset.cpp
  23. +1 −1  src/Core/OgreRenderingModule/OgreSkeletonAsset.h
  24. +16 −2 src/Core/OgreRenderingModule/Renderer.cpp
  25. +13 −2 src/Core/OgreRenderingModule/TextureAsset.cpp
  26. +2 −0  src/Core/TundraProtocolModule/Client.cpp
View
1  CMakeBuildConfigTemplate.txt
@@ -161,3 +161,4 @@ if (ENABLE_OPEN_ASSET_IMPORT)
endif ()
#AddProject(Application AssetInterestPlugin) # Options to only keep assets below certain distance threshold in memory. Can also unload all non used assets from memory. Exposed to scripts so scenes can set the behaviour.
+AddProject(Application CanvasPlugin) # Component that draws a graphics scene with any number of widgets into a mesh and provides 3D mouse input.
View
1  bin/plugins.xml
@@ -20,6 +20,7 @@
<plugin path="MumblePlugin" /> <!-- Depends on OgreRenderingModule, JavascriptModule and TundraLogicModule -->
<plugin path="VlcPlugin" /> <!-- Depends on SceneWidgetComponents -->
<plugin path="CAVEStereoModule" /> <!-- Depends on OgreRenderingModule -->
+ <plugin path="CanvasPlugin" /> <!-- Depends on OgreRenderingModule -->
<!-- TODO: Currently the above <plugin> items are loaded in the order they are specified, but below, the jsplugin items are loaded in an undefined order. Use the order specified here as the load order. -->
<!-- NOTE: The startup .js scripts are specified only by base name of the file. Don's specify a path here. Place the startup .js scripts to /bin/jsmodules/startup/. -->
View
3  bin/viewer-browser.xml
@@ -20,9 +20,10 @@
<plugin path="MumblePlugin" /> <!-- Depends on OgreRenderingModule, JavascriptModule and TundraLogicModule -->
<plugin path="VlcPlugin" /> <!-- Depends on SceneWidgetComponents -->
<plugin path="CAVEStereoModule" /> <!-- Depends on OgreRenderingModule -->
+ <plugin path="CanvasPlugin" /> <!-- Depends on OgreRenderingModule -->
<plugin path="AssetInterestPlugin" />
- <!-- TODO: Currently the above <plugin> items are loaded in the order they are specified, but below, the jsplugin items are loaded in an undefined order. Use the order specified here as the load order. -->
+ <!-- TODO: Currently the above <plugin> items are loaded in the order they are specified, but below, the jsplugin items are loaded in an undefined order. Use the order specified here as the load order. -->
<!-- NOTE: The startup .js scripts are specified only by base name of the file. Don's specify a path here. Place the startup .js scripts to /bin/jsmodules/startup/. -->
<!-- Important: The file names specified here are case sensitive even on Windows! -->
<jsplugin path="cameraapplication.js" />
View
3  bin/viewer.xml
@@ -20,7 +20,8 @@
<plugin path="MumblePlugin" /> <!-- Depends on OgreRenderingModule, JavascriptModule and TundraLogicModule -->
<plugin path="VlcPlugin" /> <!-- Depends on SceneWidgetComponents -->
<plugin path="CAVEStereoModule" /> <!-- Depends on OgreRenderingModule -->
-
+ <plugin path="CanvasPlugin" /> <!-- Depends on OgreRenderingModule -->
+
<!-- TODO: Currently the above <plugin> items are loaded in the order they are specified, but below, the jsplugin items are loaded in an undefined order. Use the order specified here as the load order. -->
<!-- NOTE: The startup .js scripts are specified only by base name of the file. Don's specify a path here. Place the startup .js scripts to /bin/jsmodules/startup/. -->
<!-- Important: The file names specified here are case sensitive even on Windows! -->
View
11 cmake/FindOgre.cmake
@@ -30,7 +30,7 @@ macro(configure_ogre)
configure_directx()
link_directx()
else()
- message(STATUS "DirectX disabled from the build")
+ message(STATUS "DirectX disabled from the build\n")
endif()
# Ogre lookup rules:
@@ -105,8 +105,13 @@ endif()
macro(link_ogre)
if (WIN32)
- target_link_libraries(${TARGET_NAME} debug OgreMain_d debug RenderSystem_Direct3D9_d)
- target_link_libraries(${TARGET_NAME} optimized OgreMain optimized RenderSystem_Direct3D9)
+ if (ENABLE_DIRECTX)
+ target_link_libraries(${TARGET_NAME} debug OgreMain_d debug RenderSystem_Direct3D9_d)
+ target_link_libraries(${TARGET_NAME} optimized OgreMain optimized RenderSystem_Direct3D9)
+ else()
+ target_link_libraries(${TARGET_NAME} debug OgreMain_d)
+ target_link_libraries(${TARGET_NAME} optimized OgreMain)
+ endif()
else()
target_link_libraries(${TARGET_NAME} ${OGRE_LIBRARY})
endif()
View
7 src/Application/JavascriptModule/JavascriptInstance.cpp
@@ -312,21 +312,22 @@ void JavascriptInstance::Run()
emit ScriptEvaluated();
}
-void JavascriptInstance::RegisterService(QObject *serviceObject, const QString &name)
+bool JavascriptInstance::RegisterService(QObject *serviceObject, const QString &name)
{
if (!engine_)
{
LogError("JavascriptInstance::RegisterService: No Qt script engine created when trying to register service to js script instance.");
- return;
+ return false;
}
if (!serviceObject)
{
LogError("JavascriptInstance::RegisterService: Trying to pass a null service object pointer to RegisterService!");
- return;
+ return false;
}
QScriptValue scriptValue = engine_->newQObject(serviceObject);
engine_->globalObject().setProperty(name, scriptValue);
+ return true;
}
void JavascriptInstance::IncludeFile(const QString &path)
View
3  src/Application/JavascriptModule/JavascriptInstance.h
@@ -53,7 +53,8 @@ class JavascriptInstance : public IScriptInstance
void Run();
/// Register new service to java script engine.
- void RegisterService(QObject *serviceObject, const QString &name);
+ /** @return Returns if the registration was successful. */
+ bool RegisterService(QObject *serviceObject, const QString &name);
//void SetPrototype(QScriptable *prototype, );
QScriptEngine* Engine() const { return engine_; }
View
19 src/Application/JavascriptModule/JavascriptModule.cpp
@@ -661,16 +661,17 @@ void JavascriptModule::PrepareScriptInstance(JavascriptInstance* instance, EC_Sc
{
QString name = properties[i];
QObject* serviceobject = framework_->property(name.toStdString().c_str()).value<QObject*>();
- instance->RegisterService(serviceobject, name);
-
- if (checked.find(serviceobject) == checked.end())
+ if (instance->RegisterService(serviceobject, name))
{
- // Check if the service object has an OnScriptEngineCreated() slot, and give it a chance to perform further actions
- const QMetaObject* meta = serviceobject->metaObject();
- if (meta->indexOfSlot("OnScriptEngineCreated(QScriptEngine*)") != -1)
- QObject::connect(this, SIGNAL(ScriptEngineCreated(QScriptEngine*)), serviceobject, SLOT(OnScriptEngineCreated(QScriptEngine*)));
-
- checked.insert(serviceobject);
+ if (checked.find(serviceobject) == checked.end())
+ {
+ // Check if the service object has an OnScriptEngineCreated() slot, and give it a chance to perform further actions
+ const QMetaObject* meta = serviceobject->metaObject();
+ if (meta->indexOfSlot("OnScriptEngineCreated(QScriptEngine*)") != -1)
+ QObject::connect(this, SIGNAL(ScriptEngineCreated(QScriptEngine*)), serviceobject, SLOT(OnScriptEngineCreated(QScriptEngine*)));
+
+ checked.insert(serviceobject);
+ }
}
}
View
55 src/Core/Asset/AssetAPI.cpp
@@ -626,8 +626,12 @@ void AssetAPI::ForgetAllAssets()
{
while(assets.size() > 0)
ForgetAsset(assets.begin()->second, false); // ForgetAsset removes the asset it is given to from the assets list, so this loop terminates.
-
assets.clear();
+
+ // We need to abort all current transfers, otherwise the transfers will call AssetTransferCompleted/Failed
+ // that will in turn load the asset to memory even if no tracking transfer is found.
+ for(AssetTransferMap::const_iterator iter = currentTransfers.begin(); iter != currentTransfers.end(); ++iter)
+ iter->second->Abort();
currentTransfers.clear();
}
@@ -1146,13 +1150,14 @@ AssetTransferMap::const_iterator AssetAPI::FindTransferIterator(IAssetTransfer *
void AssetAPI::AssetTransferCompleted(IAssetTransfer *transfer_)
{
PROFILE(AssetAPI_AssetTransferCompleted);
-
+
+ assert(transfer_);
+
// At this point, the transfer can originate from several different things:
// 1) It could be a real AssetTransfer from a real AssetProvider.
// 2) It could be an AssetTransfer to an Asset that was already downloaded before, in which case transfer_->asset is already filled and loaded at this point.
// 3) It could be an AssetTransfer that was fulfilled from the disk cache, in which case no AssetProvider was invoked to get here. (we used the readyTransfers queue for this).
-
- assert(transfer_);
+
AssetTransferPtr transfer = transfer_->shared_from_this(); // Elevate to a SharedPtr immediately to keep at least one ref alive of this transfer for the duration of this function call.
//LogDebug("Transfer of asset \"" + transfer->assetType + "\", name \"" + transfer->source.ref + "\" succeeded.");
@@ -1205,29 +1210,19 @@ void AssetAPI::AssetTransferCompleted(IAssetTransfer *transfer_)
// Tell everyone this transfer has now been downloaded. Note that when this signal is fired, the asset dependencies may not yet be loaded.
transfer->EmitAssetDownloaded();
+ bool success = false;
const u8 *data = (transfer->rawAssetData.size() > 0 ? &transfer->rawAssetData[0] : 0);
if (data)
- transfer->asset->LoadFromFileInMemory(data, transfer->rawAssetData.size());
+ success = transfer->asset->LoadFromFileInMemory(data, transfer->rawAssetData.size());
else
- transfer->asset->LoadFromFile(transfer->asset->DiskSource());
-
- //bool success = transfer->asset->LoadFromFileInMemory(data, transfer->rawAssetData.size());
- //if (!success)
- //{
- // QString error("AssetAPI: Failed to load " + transfer->assetType + " '" + transfer->source.ref + "' from asset data.");
- // transfer->EmitAssetFailed(error);
- // return;
- //}
+ success = transfer->asset->LoadFromFile(transfer->asset->DiskSource());
- //if (diskSourceChangeWatcher && !transfer->asset->DiskSource().isEmpty())
- // diskSourceChangeWatcher->addPath(transfer->asset->DiskSource());
-
- //// If this asset depends on any other assets, we have to make asset requests for those assets as well (and all assets that they refer to, and so on).
- //RequestAssetDependencies(transfer->asset);
-
- //// If we don't have any outstanding dependencies, succeed and remove the transfer
- //if (NumPendingDependencies(transfer->asset) == 0)
- // AssetDependenciesCompleted(transfer);
+ // If the load from either of in memory data or file data failed, update the internal state.
+ // Otherwise the transfer will be left dangling in currentTransfers. For successful loads
+ // we do no need to call AssetLoadCompleted because success can mean asynchronous loading,
+ // in which case the call will arrive once the asynchronous loading is completed.
+ if (!success)
+ AssetLoadFailed(transfer->asset->Name());
}
void AssetAPI::AssetTransferFailed(IAssetTransfer *transfer, QString reason)
@@ -1235,7 +1230,7 @@ void AssetAPI::AssetTransferFailed(IAssetTransfer *transfer, QString reason)
assert(transfer);
if (!transfer)
return;
-
+
LogError("Transfer of asset \"" + transfer->assetType + "\", name \"" + transfer->source.ref + "\" failed! Reason: \"" + reason + "\"");
///\todo In this function, there is a danger of reaching an infinite recursion. Remember recursion parents and avoid infinite loops. (A -> B -> C -> A)
@@ -1322,9 +1317,8 @@ void AssetAPI::AssetLoadCompleted(const QString assetRef)
// If we don't have any outstanding dependencies
// for the transfer, succeed and remove the transfer
- if (iter != currentTransfers.end())
- if (!HasPendingDependencies(asset))
- AssetDependenciesCompleted(iter->second);
+ if (iter != currentTransfers.end() && !HasPendingDependencies(asset))
+ AssetDependenciesCompleted(iter->second);
}
else
LogError("AssetAPI: Asset \"" + assetRef + "\" load completed, but no corresponding transfer or existing asset is being tracked!");
@@ -1332,15 +1326,14 @@ void AssetAPI::AssetLoadCompleted(const QString assetRef)
void AssetAPI::AssetLoadFailed(const QString assetRef)
{
- AssetTransferMap::const_iterator iter = FindTransferIterator(assetRef);
+ AssetTransferMap::iterator iter = FindTransferIterator(assetRef);
AssetMap::const_iterator iter2 = assets.find(assetRef);
if (iter != currentTransfers.end())
{
AssetTransferPtr transfer = iter->second;
-
- QString error("AssetAPI: Failed to load " + transfer->assetType + " '" + transfer->source.ref + "' from asset data.");
- transfer->EmitAssetFailed(error);
+ transfer->EmitAssetFailed("Failed to load " + transfer->assetType + " '" + transfer->source.ref + "' from asset data.");
+ currentTransfers.erase(iter);
}
else if (iter2 != assets.end())
LogError("AssetAPI: Failed to reload asset '" + iter2->second->Name());
View
14 src/Core/Asset/IAsset.cpp
@@ -139,6 +139,12 @@ bool IAsset::LoadFromFile(QString filename)
{
PROFILE(IAsset_LoadFromFile);
filename = filename.trimmed(); ///\todo Sanitate.
+ if (filename.isEmpty())
+ {
+ LogDebug("LoadFromFile failed for asset \"" + Name() + "\", given file path is empty!");
+ return false;
+ }
+
std::vector<u8> fileData;
bool success = LoadFileToVector(filename, fileData);
if (!success)
@@ -167,12 +173,8 @@ bool IAsset::LoadFromFileInMemory(const u8 *data, size_t numBytes, bool allowAsy
LogDebug("LoadFromFileInMemory failed for asset \"" + ToString() + "\"! No data present!");
return false;
}
-
- bool success = DeserializeFromData(data, numBytes, allowAsynchronous);
- /// Automatically call AssetAPI::AssetLoadFailed if load failed.
- if (!success)
- assetAPI->AssetLoadFailed(Name());
- return success;
+
+ return DeserializeFromData(data, numBytes, allowAsynchronous);
}
void IAsset::DependencyLoaded(AssetPtr dependee)
View
12 src/Core/Asset/IAssetProvider.h
@@ -27,12 +27,18 @@ class IAssetProvider
or if the provider in question does not need the type information, this can be left blank. */
virtual bool IsValidRef(QString assetRef, QString assetType) = 0;
+ /// Request asset with assetRef and assetType from this provider.
+ /** @return Initiated asset transfer. Note that this can be null. */
virtual AssetTransferPtr RequestAsset(QString assetRef, QString assetType) = 0;
+
+ /// Aborts the ongoing transfer, returns true if successful and false otherwise.
+ /** Override this function in a provider implementation if it supports aborting. */
+ virtual bool AbortTransfer(IAssetTransfer *transfer) { return false; }
/// Performs time-based update of asset provider, to for example handle timeouts.
- /// The system will call this periodically for all registered asset providers, so
- /// it does not need to be called manually.
- /// @param frametime Seconds since last frame
+ /** The system will call this periodically for all registered asset providers, so
+ it does not need to be called manually.
+ @param frametime Seconds since last frame */
virtual void Update(f64 frametime) {}
/// Issues an asset deletion request to the asset storage and provider this asset resides in.
View
70 src/Core/Asset/IAssetTransfer.cpp
@@ -1,6 +1,21 @@
+// For conditions of distribution and use, see copyright notice in LICENSE
+
#include "IAssetTransfer.h"
+#include "IAssetProvider.h"
#include "IAsset.h"
+
#include "Profiler.h"
+#include "LoggingFunctions.h"
+
+IAssetTransfer::IAssetTransfer() :
+ cachingAllowed(true),
+ diskSourceType(IAsset::Original)
+{
+}
+
+IAssetTransfer::~IAssetTransfer()
+{
+}
void IAssetTransfer::EmitAssetDownloaded()
{
@@ -17,3 +32,58 @@ void IAssetTransfer::EmitAssetFailed(QString reason)
{
emit Failed(this, reason);
}
+
+bool IAssetTransfer::Abort()
+{
+ if (provider.lock().get())
+ return provider.lock()->AbortTransfer(this);
+ else
+ {
+ LogWarning("IAssetTransfer::Abort() Provider is null, cannot call IAssetProvider::AbortTransfer().");
+ return false;
+ }
+}
+
+void IAssetTransfer::SetCachingBehavior(bool cachingAllowed, QString diskSource)
+{
+ this->cachingAllowed = cachingAllowed;
+ this->diskSource = diskSource;
+}
+
+QString IAssetTransfer::DiskSource() const
+{
+ return diskSource;
+}
+
+IAsset::SourceType IAssetTransfer::DiskSourceType() const
+{
+ return diskSourceType;
+}
+
+bool IAssetTransfer::CachingAllowed() const
+{
+ return cachingAllowed;
+}
+
+QByteArray IAssetTransfer::RawData() const
+{
+ if (rawAssetData.size() == 0)
+ return QByteArray();
+ else
+ return QByteArray::fromRawData((const char*)&rawAssetData[0], rawAssetData.size());
+}
+
+QString IAssetTransfer::SourceUrl() const
+{
+ return source.ref;
+}
+
+QString IAssetTransfer::AssetType() const
+{
+ return assetType;
+}
+
+AssetPtr IAssetTransfer::Asset() const
+{
+ return asset;
+}
View
77 src/Core/Asset/IAssetTransfer.h
@@ -16,15 +16,11 @@
/// Represents a currently ongoing asset download operation.
class IAssetTransfer : public QObject, public boost::enable_shared_from_this<IAssetTransfer>
{
- Q_OBJECT
+Q_OBJECT
public:
- IAssetTransfer()
- :cachingAllowed(true),diskSourceType(IAsset::Original)
- {
- }
-
- virtual ~IAssetTransfer() {}
+ IAssetTransfer();
+ virtual ~IAssetTransfer();
/// Points to the actual asset if it has been loaded in.
AssetPtr asset;
@@ -47,40 +43,60 @@ class IAssetTransfer : public QObject, public boost::enable_shared_from_this<IAs
/// Specifies the storage this asset is being downloaded from.
AssetStorageWeakPtr storage;
+ /// Emits Downloaded signal.
void EmitAssetDownloaded();
+ /// Emits Succeeded signal.
void EmitTransferSucceeded();
+ /// Emits Failed signal with reason.
void EmitAssetFailed(QString reason);
/// Stores the raw asset bytes for this asset.
std::vector<u8> rawAssetData;
public slots:
- /// Returns the current transfer progress in the range [0, 1].
- // float Progress() const;
-
- /// Contract between IAssetProvider and AssetAPI: IAssetProvider is expected to call this upon completion of the transfer,
- /// to specify whether the results of this transfer are allowed to be cached, and to specify an existing local disk source
- /// for the asset, if exists.
- /// @param cachingAllowed If true, the Asset API will create a new cached file to the system asset cache folder that will be
- /// used if this asset is requested in the future. This cache persists on disk over application shutdown.
- /// @param diskSource_ If specified, this local filename will be used as a cache file for the resulting asset. When a reload
- /// of this asset is requested (locally), this file path will be used to reload the asset. If allowCaching==true,
- /// this field has no effect, as diskSource will be created to be a filename in the asset cache.
- void SetCachingBehavior(bool cachingAllowed_, QString diskSource_) { cachingAllowed = cachingAllowed_; diskSource = diskSource_; }
-
- QString DiskSource() const { return diskSource; }
+ /// Aborts the transfer immediately. Override this function in a subclass implementation.
+ /** @note Default IAssetTransfer implementation logs a not implemented warning and return false.
+ @return True if abort was successful, false otherwise. */
+ virtual bool Abort();
+
+ /// Set caching behavior and disk source. Can be set after the transfer has been requested to determine disk caching.
+ /** Contract between IAssetProvider and AssetAPI: IAssetProvider is expected to call this upon completion of the transfer,
+ to specify whether the results of this transfer are allowed to be cached, and to specify an existing local disk source
+ for the asset, if exists.
+ @param cachingAllowed If true, the Asset API will create a new cached file to the system asset cache folder that will be
+ used if this asset is requested in the future. This cache persists on disk over application shutdown.
+ @param diskSource_ If specified, this local filename will be used as a cache file for the resulting asset. When a reload
+ of this asset is requested (locally), this file path will be used to reload the asset. If allowCaching==true,
+ this field has no effect, as diskSource will be created to be a filename in the asset cache. */
+ void SetCachingBehavior(bool cachingAllowed, QString diskSource);
+
+ /// Returns the disk source of this transfer.
+ QString DiskSource() const;
+
+ /// Returns the disk source type of this transfer.
+ IAsset::SourceType DiskSourceType() const;
+
+ /// Returns if this transfer allows caching to a disk source.
+ bool CachingAllowed() const;
- IAsset::SourceType DiskSourceType() const { return diskSourceType; }
+ /// Return the transfers raw data as a script friendly QByteArray.
+ /** @note Will be empty until Downloaded is emitted */
+ QByteArray RawData() const;
+
+ /// Returns source URL.
+ QString SourceUrl() const;
+
+ /// Return asset type.
+ QString AssetType() const;
- bool CachingAllowed() const { return cachingAllowed; }
+ /// Return asset object.
+ /** @note Will be null until Succeeded is emitted */
+ AssetPtr Asset() const;
- // Script getters for public attributes
- QByteArray RawData() const { if (rawAssetData.size() == 0) return QByteArray(); else return QByteArray::fromRawData((const char*)&rawAssetData[0], rawAssetData.size()); }
- QString SourceUrl() const { return source.ref; }
- QString AssetType() const { return assetType; }
- AssetPtr Asset() const { return asset; }
+ /// @todo Returns the current transfer progress in the range [0, 1].
+ // float Progress() const;
signals:
/// Emitted when the raw byte download of this asset finishes. The asset pointer is set at this point.
@@ -93,13 +109,12 @@ public slots:
void Failed(IAssetTransfer *transfer, QString reason);
private:
- bool cachingAllowed;
-
QString diskSource;
+ bool cachingAllowed;
+
};
/// Virtual asset transfer for assets that have already been loaded, but are re-requested
class VirtualAssetTransfer : public IAssetTransfer
{
};
-
View
27 src/Core/AssetModule/HttpAssetProvider.cpp
@@ -238,6 +238,28 @@ AssetTransferPtr HttpAssetProvider::RequestAsset(QString assetRef, QString asset
return transfer;
}
+bool HttpAssetProvider::AbortTransfer(IAssetTransfer *transfer)
+{
+ if (!transfer)
+ return false;
+
+ for (TransferMap::iterator iter = transfers.begin(); iter != transfers.end(); ++iter)
+ {
+ AssetTransferPtr ongoingTransfer = iter->second;
+ if (ongoingTransfer.get() == transfer)
+ {
+ transfer->EmitAssetFailed("Transfer aborted.");
+ transfers.erase(iter);
+
+ // Abort last as it will invoke a call to OnHttpTransferFinished.
+ if (iter->first)
+ iter->first->abort();
+ return true;
+ }
+ }
+ return false;
+}
+
AssetUploadTransferPtr HttpAssetProvider::UploadAssetFromFileInMemory(const u8 *data, size_t numBytes, AssetStoragePtr destination, const QString &assetName)
{
if (!networkAccessManager)
@@ -356,12 +378,11 @@ void HttpAssetProvider::OnHttpTransferFinished(QNetworkReply *reply)
{
case QNetworkAccessManager::GetOperation:
{
+ // If the transfer is not in our transfers map it was aborted via AbortTransfer.
TransferMap::iterator iter = transfers.find(reply);
if (iter == transfers.end())
- {
- LogError("GetOperation: Received a finish signal of an unknown Http transfer!");
return;
- }
+
HttpAssetTransferPtr transfer = iter->second;
assert(transfer);
transfer->rawAssetData.clear();
View
4 src/Core/AssetModule/HttpAssetProvider.h
@@ -40,8 +40,12 @@ class ASSET_MODULE_API HttpAssetProvider : public QObject, public IAssetProvider
/** @return true if this asset provider can handle the id */
virtual bool IsValidRef(QString assetRef, QString assetType = "");
+ /// Request a http asset, returns resulted transfer.
virtual AssetTransferPtr RequestAsset(QString assetRef, QString assetType);
+ /// Aborts the ongoing http transfer.
+ virtual bool AbortTransfer(IAssetTransfer *transfer);
+
/// Adds the given http URL to the list of current asset storages.
/// Returns the newly created storage, or 0 if a storage with the given name already existed, or if some other error occurred.
/// @param storageName An identifier for the storage. Remember that Asset Storage names are case-insensitive.
View
2  src/Core/AssetModule/HttpAssetTransfer.h
@@ -7,7 +7,7 @@
/// Utility class for identifying HTTP asset transfers for another types of asset transfers.
class HttpAssetTransfer : public IAssetTransfer
{
- Q_OBJECT
+Q_OBJECT
public:
View
20 src/Core/AssetModule/LocalAssetProvider.cpp
@@ -88,6 +88,24 @@ AssetTransferPtr LocalAssetProvider::RequestAsset(QString assetRef, QString asse
return transfer;
}
+bool LocalAssetProvider::AbortTransfer(IAssetTransfer *transfer)
+{
+ if (!transfer)
+ return false;
+
+ for (std::vector<AssetTransferPtr>::iterator iter = pendingDownloads.begin(); iter != pendingDownloads.end(); ++iter)
+ {
+ AssetTransferPtr ongoingTransfer = (*iter);
+ if (ongoingTransfer.get() == transfer)
+ {
+ transfer->EmitAssetFailed("Transfer aborted.");
+ pendingDownloads.erase(iter);
+ return true;
+ }
+ }
+ return false;
+}
+
QString LocalAssetProvider::GetPathForAsset(const QString &assetRef, LocalAssetStoragePtr *storage) const
{
QString path;
@@ -289,7 +307,7 @@ void LocalAssetProvider::CompletePendingFileDownloads()
AssetTransferPtr transfer = pendingDownloads.back();
pendingDownloads.pop_back();
-
+
QString ref = transfer->source.ref;
QString path_filename;
View
6 src/Core/AssetModule/LocalAssetProvider.h
@@ -29,8 +29,12 @@ class ASSET_MODULE_API LocalAssetProvider : public QObject, public IAssetProvide
/// Checks an asset id for validity
/** @return true if this asset provider can handle the id */
virtual bool IsValidRef(QString assetRef, QString assetType);
-
+
+ /// Requests a local asset, returns resulted transfer.
virtual AssetTransferPtr RequestAsset(QString assetRef, QString assetType);
+
+ /// Aborts the ongoing local transfer.
+ virtual bool AbortTransfer(IAssetTransfer *transfer);
/// Performs time-based update
/** @param frametime Seconds since last frame */
View
4 src/Core/OgreRenderingModule/EC_RttTarget.cpp
@@ -31,7 +31,9 @@ EC_RttTarget::EC_RttTarget(Scene* scene) :
EC_RttTarget::~EC_RttTarget()
{
- if (!ViewEnabled())
+ // Cannot use ViewEnabled() here, the parent entity is already null,
+ // which means it will return true. After that we will crash below calling Ogre.
+ if (framework->IsHeadless())
return;
//XXX didn't have a ref to renderer here yet. is this really required?
View
16 src/Core/OgreRenderingModule/OgreMeshAsset.cpp
@@ -115,11 +115,8 @@ bool OgreMeshAsset::DeserializeFromData(const u8 *data_, size_t numBytes, bool a
assetAPI->AssetLoadCompleted(Name());
return true;
}
-
else
- {
return false;
- }
}
struct KdTreeRayQueryFirstHitVisitor
@@ -386,7 +383,10 @@ void OgreMeshAsset::operationCompleted(Ogre::BackgroundProcessTicket ticket, con
{
if (ticket != loadTicket_)
return;
-
+
+ // Reset to 0 to mark the asynch request is not active anymore. Aborted in Unload() if it is.
+ loadTicket_ = 0;
+
const QString assetRef = Name();
if (!result.error)
{
@@ -416,6 +416,14 @@ void OgreMeshAsset::operationCompleted(Ogre::BackgroundProcessTicket ticket, con
void OgreMeshAsset::DoUnload()
{
+ // If a ongoing asynchronous asset load requested has been made to ogre, we need to abort it.
+ // Otherwise Ogre will crash to our raw pointer that was passed if we get deleted. A ongoing ticket id cannot be 0.
+ if (loadTicket_ != 0)
+ {
+ Ogre::ResourceBackgroundQueue::getSingleton().abortRequest(loadTicket_);
+ loadTicket_ = 0;
+ }
+
if (ogreMesh.isNull())
return;
View
4 src/Core/OgreRenderingModule/OgreMeshAsset.h
@@ -20,8 +20,8 @@ class OGRE_MODULE_API OgreMeshAsset : public IAsset, Ogre::ResourceBackgroundQue
Q_OBJECT
public:
- OgreMeshAsset(AssetAPI *owner, const QString &type_, const QString &name_)
- :IAsset(owner, type_, name_)
+ OgreMeshAsset(AssetAPI *owner, const QString &type_, const QString &name_) :
+ IAsset(owner, type_, name_), loadTicket_(0)
{
}
View
11 src/Core/OgreRenderingModule/OgreSkeletonAsset.cpp
@@ -99,6 +99,9 @@ void OgreSkeletonAsset::operationCompleted(Ogre::BackgroundProcessTicket ticket,
if (ticket != loadTicket_)
return;
+ // Reset to 0 to mark the asynch request is not active anymore. Aborted in Unload() if it is.
+ loadTicket_ = 0;
+
const QString assetRef = Name();
internal_name_ = AssetAPI::SanitateAssetRef(assetRef).toStdString();
if (!result.error)
@@ -149,6 +152,14 @@ bool OgreSkeletonAsset::SerializeTo(std::vector<u8> &data, const QString &serial
void OgreSkeletonAsset::DoUnload()
{
+ // If a ongoing asynchronous asset load requested has been made to ogre, we need to abort it.
+ // Otherwise Ogre will crash to our raw pointer that was passed if we get deleted. A ongoing ticket id cannot be 0.
+ if (loadTicket_ != 0)
+ {
+ Ogre::ResourceBackgroundQueue::getSingleton().abortRequest(loadTicket_);
+ loadTicket_ = 0;
+ }
+
if (ogreSkeleton.isNull())
return;
View
2  src/Core/OgreRenderingModule/OgreSkeletonAsset.h
@@ -19,7 +19,7 @@ Q_OBJECT
public:
/// Constructor.
OgreSkeletonAsset(AssetAPI *owner, const QString &type_, const QString &name_) :
- IAsset(owner, type_, name_)
+ IAsset(owner, type_, name_), loadTicket_(0)
{
}
View
18 src/Core/OgreRenderingModule/Renderer.cpp
@@ -692,6 +692,18 @@ namespace OgreRenderer
if ((uint)dirty.bottom() > desc.Height) dirty.setBottom(desc.Height);
if (dirty.left() > dirty.right()) dirty.setLeft(dirty.right());
if (dirty.top() > dirty.bottom()) dirty.setTop(dirty.bottom());
+
+ // If graphics items are moved/animated outside the scene rect some dirty rect(s) can be outside the scene rect, check for it.
+ if (!viewrect.contains(dirty.topLeft()) || !viewrect.contains(dirty.bottomRight()))
+ {
+ if (viewrect.intersects(dirty))
+ {
+ LogWarning(QString("Renderer::Render: Dirty rect %1,%2 %3x%4 not inside view %5,%6 %7x%8, correcting.")
+ .arg(dirty.x()).arg(dirty.y()).arg(dirty.width()).arg(dirty.height())
+ .arg(viewrect.x()).arg(viewrect.y()).arg(viewrect.width()).arg(viewrect.height()));
+ dirty = viewrect.intersected(dirty);
+ }
+ }
const int copyableHeight = min<int>(dirty.height(), min<int>(view->BackBuffer()->height() - dirty.top(), desc.Height - dirty.top()));
const int copyableWidthBytes = 4*min<int>(dirty.width(), min<int>(view->BackBuffer()->width() - dirty.left(), desc.Width - dirty.left()));
@@ -705,12 +717,14 @@ namespace OgreRenderer
{
PROFILE(LockRect);
- // HRESULT hr = surface->LockRect(&lock, 0, D3DLOCK_DISCARD); // for full UI redraw.
+ //HRESULT hr = surface->LockRect(&lock, 0, D3DLOCK_DISCARD); // for full UI redraw.
RECT lockRect = { dirty.left(), dirty.top(), dirty.right(), dirty.bottom() };
HRESULT hr = surface->LockRect(&lock, &lockRect, 0);
if (FAILED(hr))
{
- LogError("Renderer::Render: D3D9SURFACE9::LockRect failed!");
+ LogError(QString("Renderer::Render: D3D9SURFACE9::LockRect failed (view %1,%2 %3x%4 rect %5,%6 %7x%8)")
+ .arg(viewrect.x()).arg(viewrect.y()).arg(viewrect.width()).arg(viewrect.height())
+ .arg(dirty.x()).arg(dirty.y()).arg(dirty.width()).arg(dirty.height()));
return; // Instead of returning, could try doing a full surface lock. See commented line above.
}
assert((uint)lock.Pitch >= desc.Width*4);
View
15 src/Core/OgreRenderingModule/TextureAsset.cpp
@@ -36,8 +36,8 @@
#include "MemoryLeakCheck.h"
-TextureAsset::TextureAsset(AssetAPI *owner, const QString &type_, const QString &name_)
-:IAsset(owner, type_, name_)
+TextureAsset::TextureAsset(AssetAPI *owner, const QString &type_, const QString &name_) :
+ IAsset(owner, type_, name_), loadTicket_(0)
{
ogreAssetName = AssetAPI::SanitateAssetRef(this->Name().toStdString()).c_str();
}
@@ -198,6 +198,9 @@ void TextureAsset::operationCompleted(Ogre::BackgroundProcessTicket ticket, cons
{
if (ticket != loadTicket_)
return;
+
+ // Reset to 0 to mark the asynch request is not active anymore. Aborted in Unload() if it is.
+ loadTicket_ = 0;
const QString assetRef = Name();
ogreAssetName = AssetAPI::SanitateAssetRef(assetRef);
@@ -278,6 +281,14 @@ bool TextureAsset::SerializeTo(std::vector<u8> &data, const QString &serializati
void TextureAsset::DoUnload()
{
+ // If a ongoing asynchronous asset load requested has been made to ogre, we need to abort it.
+ // Otherwise Ogre will crash to our raw pointer that was passed if we get deleted. A ongoing ticket id cannot be 0.
+ if (loadTicket_ != 0)
+ {
+ Ogre::ResourceBackgroundQueue::getSingleton().abortRequest(loadTicket_);
+ loadTicket_ = 0;
+ }
+
if (!ogreTexture.isNull())
ogreAssetName = ogreTexture->getName().c_str();
View
2  src/Core/TundraProtocolModule/Client.cpp
@@ -18,6 +18,7 @@
#include "CoreStringUtils.h"
#include "SceneAPI.h"
#include "Scene.h"
+#include "AssetAPI.h"
#include "Application.h"
#include <kNet.h>
@@ -190,6 +191,7 @@ void Client::DoLogout(bool fail)
client_id_ = 0;
framework_->Scene()->RemoveScene("TundraClient");
+ framework_->Asset()->ForgetAllAssets();
emit Disconnected();
}
Something went wrong with that request. Please try again.