Browse Source

Remove ResourceManager::instance() singleton deprecated in 2019.10.

Due to it being global it was severely limiting multithreaded
applications and removing it was the path of least resistance.

Good riddance.
dpi-change-events
Vladimír Vondruš 6 years ago
parent
commit
e7aeaf78d0
  1. 4
      doc/changelog.dox
  2. 1
      src/Magnum/CMakeLists.txt
  3. 6
      src/Magnum/DebugTools/ForceRenderer.cpp
  4. 6
      src/Magnum/DebugTools/ObjectRenderer.cpp
  5. 9
      src/Magnum/DebugTools/ResourceManager.cpp
  6. 2
      src/Magnum/DebugTools/ResourceManager.h
  7. 82
      src/Magnum/ResourceManager.h
  8. 56
      src/Magnum/ResourceManager.hpp
  9. 14
      src/Magnum/Test/CMakeLists.txt
  10. 55
      src/Magnum/Test/ResourceManagerLocalInstanceTest.cpp
  11. 45
      src/Magnum/Test/ResourceManagerLocalInstanceTestLib.cpp
  12. 50
      src/Magnum/Test/ResourceManagerLocalInstanceTestLib.h

4
doc/changelog.dox

@ -327,6 +327,10 @@ See also:
@p segments parameter isn't divisible by four. Before it mistakenly @p segments parameter isn't divisible by four. Before it mistakenly
asserted only if @p segments wasn't divisible by two, and now code that asserted only if @p segments wasn't divisible by two, and now code that
mistakenly used a disallowed value will start asserting. mistakenly used a disallowed value will start asserting.
- @ref ResourceManager singleton accessible through @cpp instance() @ce that
was deprecated in 2019.10 is now removed. Usually a deprecated feature is
kept for at least a year before removal, but in this case it was severely
limiting multithreaded applications and removing it was necessary.
- Removed remaining APIs deprecated in version 2018.04: - Removed remaining APIs deprecated in version 2018.04:
- @cpp Audio::Buffer::Format @ce, use @ref Audio::BufferFormat instead - @cpp Audio::Buffer::Format @ce, use @ref Audio::BufferFormat instead
- @cpp Shaders::*Vector::setVectorTexture() @ce, - @cpp Shaders::*Vector::setVectorTexture() @ce,

1
src/Magnum/CMakeLists.txt

@ -56,7 +56,6 @@ set(Magnum_HEADERS
PixelStorage.h PixelStorage.h
Resource.h Resource.h
ResourceManager.h ResourceManager.h
ResourceManager.hpp
Sampler.h Sampler.h
Tags.h Tags.h
Timeline.h Timeline.h

6
src/Magnum/DebugTools/ForceRenderer.cpp

@ -77,12 +77,6 @@ template<UnsignedInt dimensions> ForceRenderer<dimensions>::ForceRenderer(Resour
manager.set(_mesh.key(), std::move(mesh), ResourceDataState::Final, ResourcePolicy::Manual); manager.set(_mesh.key(), std::move(mesh), ResourceDataState::Final, ResourcePolicy::Manual);
} }
#ifdef MAGNUM_BUILD_DEPRECATED
CORRADE_IGNORE_DEPRECATED_PUSH
template<UnsignedInt dimensions> ForceRenderer<dimensions>::ForceRenderer(SceneGraph::AbstractObject<dimensions, Float>& object, const VectorTypeFor<dimensions, Float>& forcePosition, const VectorTypeFor<dimensions, Float>& force, ResourceKey options, SceneGraph::DrawableGroup<dimensions, Float>* drawables): ForceRenderer<dimensions>{static_cast<ResourceManager&>(ResourceManager::instance()), object, forcePosition, force, options, drawables} {}
CORRADE_IGNORE_DEPRECATED_POP
#endif
/* To avoid deleting pointers to incomplete type on destruction of Resource members */ /* To avoid deleting pointers to incomplete type on destruction of Resource members */
template<UnsignedInt dimensions> ForceRenderer<dimensions>::~ForceRenderer() = default; template<UnsignedInt dimensions> ForceRenderer<dimensions>::~ForceRenderer() = default;

6
src/Magnum/DebugTools/ObjectRenderer.cpp

@ -65,12 +65,6 @@ template<UnsignedInt dimensions> ObjectRenderer<dimensions>::ObjectRenderer(Reso
if(!_mesh) manager.set<GL::Mesh>(_mesh.key(), MeshTools::compile(Renderer<dimensions>::meshData())); if(!_mesh) manager.set<GL::Mesh>(_mesh.key(), MeshTools::compile(Renderer<dimensions>::meshData()));
} }
#ifdef MAGNUM_BUILD_DEPRECATED
CORRADE_IGNORE_DEPRECATED_PUSH
template<UnsignedInt dimensions> ObjectRenderer<dimensions>::ObjectRenderer(SceneGraph::AbstractObject<dimensions, Float>& object, ResourceKey options, SceneGraph::DrawableGroup<dimensions, Float>* drawables): ObjectRenderer<dimensions>{static_cast<ResourceManager&>(ResourceManager::instance()), object, options, drawables} {}
CORRADE_IGNORE_DEPRECATED_POP
#endif
/* To avoid deleting pointers to incomplete type on destruction of Resource members */ /* To avoid deleting pointers to incomplete type on destruction of Resource members */
template<UnsignedInt dimensions> ObjectRenderer<dimensions>::~ObjectRenderer() = default; template<UnsignedInt dimensions> ObjectRenderer<dimensions>::~ObjectRenderer() = default;

9
src/Magnum/DebugTools/ResourceManager.cpp

@ -25,7 +25,6 @@
#include "ResourceManager.h" #include "ResourceManager.h"
#include "Magnum/ResourceManager.hpp"
#include "Magnum/DebugTools/ForceRenderer.h" #include "Magnum/DebugTools/ForceRenderer.h"
#include "Magnum/DebugTools/ObjectRenderer.h" #include "Magnum/DebugTools/ObjectRenderer.h"
#include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/GL/AbstractShaderProgram.h"
@ -33,13 +32,7 @@
#include "Magnum/GL/Mesh.h" #include "Magnum/GL/Mesh.h"
#include "Magnum/GL/MeshView.h" #include "Magnum/GL/MeshView.h"
namespace Magnum { namespace Magnum { namespace DebugTools {
namespace Implementation {
template struct MAGNUM_DEBUGTOOLS_EXPORT ResourceManagerLocalInstanceImplementation<ResourceManagerLocalInstance, GL::AbstractShaderProgram, GL::Buffer, GL::Mesh, GL::MeshView, DebugTools::ForceRendererOptions, DebugTools::ObjectRendererOptions>;
}
namespace DebugTools {
ResourceManager::ResourceManager() { ResourceManager::ResourceManager() {
setFallback(new ForceRendererOptions); setFallback(new ForceRendererOptions);

2
src/Magnum/DebugTools/ResourceManager.h

@ -62,7 +62,7 @@ information.
@ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See
@ref building-features for more information. @ref building-features for more information.
*/ */
class MAGNUM_DEBUGTOOLS_EXPORT ResourceManager: public Magnum::ResourceManager<Magnum::Implementation::ResourceManagerLocalInstance, GL::AbstractShaderProgram, GL::Buffer, GL::Mesh, GL::MeshView, DebugTools::ForceRendererOptions, DebugTools::ObjectRendererOptions> class MAGNUM_DEBUGTOOLS_EXPORT ResourceManager: public Magnum::ResourceManager<GL::AbstractShaderProgram, GL::Buffer, GL::Mesh, GL::MeshView, DebugTools::ForceRendererOptions, DebugTools::ObjectRendererOptions>
{ {
public: public:
explicit ResourceManager(); explicit ResourceManager();

82
src/Magnum/ResourceManager.h

@ -159,33 +159,12 @@ template<class T> class ResourceManagerData {
/* Helper class for defining which real types are in the type pack */ /* Helper class for defining which real types are in the type pack */
template<class...> struct ResourceTypePack {}; template<class...> struct ResourceTypePack {};
/* Common resource manager implementation with inline internal instance (for
use in user code), definition of internalInstance() is in this header */
template<class... Types> struct ResourceManagerInlineInstanceImplementation {
static ResourceManager<Types...>*& internalInstance();
};
template<class ...Types> struct ResourceManagerImplementation: ResourceManagerInlineInstanceImplementation<Types...>, ResourceManagerData<Types>... {
typedef ResourceTypePack<Types...> TypePack;
};
/* Resource manager implementation with file-local internal instance (for use
in code where the manager is used across library boundaries), definition
of internalInstance() is in ResourceManager.hpp, see it for usage details */
template<class... Types> struct ResourceManagerLocalInstanceImplementation {
static ResourceManager<Types...>*& internalInstance();
};
struct ResourceManagerLocalInstance;
template<class ...Types> struct ResourceManagerImplementation<ResourceManagerLocalInstance, Types...>: ResourceManagerLocalInstanceImplementation<ResourceManagerLocalInstance, Types...>, ResourceManagerData<Types>... {
typedef ResourceTypePack<Types...> TypePack;
};
} }
/** /**
@brief Resource manager @brief Resource manager
Provides storage for arbitrary set of types, accessible globally using Provides storage for arbitrary set of types.
@ref instance().
@section ResourceMananger-usage Usage @section ResourceMananger-usage Usage
@ -244,35 +223,16 @@ Basic usage is:
/* Due to too much work involved with explicit template instantiation (all /* Due to too much work involved with explicit template instantiation (all
Resource combinations, all ResourceManagerData...), this class doesn't have Resource combinations, all ResourceManagerData...), this class doesn't have
template implementation file. */ template implementation file. */
template<class... Types> class ResourceManager: private Implementation::ResourceManagerImplementation<Types>... { template<class... Types> class ResourceManager: private Implementation::ResourceManagerData<Types>... {
public: public:
#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Constructor */
/**
* @brief Global instance
*
* Assumes that the instance exists.
*
* @m_deprecated_since{2019,10} Implicit @ref ResourceManager singleton
* is deprecated, make your own or pass a reference around instead
*/
static CORRADE_DEPRECATED("implicit ResourceManager singleton is deprecated, make your own or pass a reference around instead") ResourceManager<Types...>& instance();
#endif
/**
* @brief Constructor
*
* Sets global instance pointer to itself.
* @attention Only one instance of given ResourceManager type can be
* created.
* @see @ref instance()
*/
explicit ResourceManager(); explicit ResourceManager();
/** /**
* @brief Destructor * @brief Destructor
* *
* Sets global instance pointer to @cpp nullptr @ce. * Expects that all resources are not referenced anymore at the point
* @see @ref instance() * of destruction.
*/ */
~ResourceManager(); ~ResourceManager();
@ -413,7 +373,7 @@ template<class... Types> class ResourceManager: private Implementation::Resource
* @return Reference to self (for method chaining) * @return Reference to self (for method chaining)
*/ */
ResourceManager<Types...>& free() { ResourceManager<Types...>& free() {
freeInternal(typename Implementation::ResourceManagerImplementation<Types...>::TypePack{}); freeInternal(Implementation::ResourceTypePack<Types...>{});
return *this; return *this;
} }
@ -437,7 +397,7 @@ template<class... Types> class ResourceManager: private Implementation::Resource
* referenced. * referenced.
*/ */
ResourceManager<Types...>& clear() { ResourceManager<Types...>& clear() {
clearInternal(typename Implementation::ResourceManagerImplementation<Types...>::TypePack{}); clearInternal(Implementation::ResourceTypePack<Types...>{});
return *this; return *this;
} }
@ -493,11 +453,6 @@ template<class... Types> class ResourceManager: private Implementation::Resource
namespace Implementation { namespace Implementation {
template<class ...Types> ResourceManager<Types...>*& ResourceManagerInlineInstanceImplementation<Types...>::internalInstance() {
static ResourceManager<Types...>* _instance(nullptr);
return _instance;
}
template<class T> void safeDelete(T* data) { template<class T> void safeDelete(T* data) {
static_assert(sizeof(T) > 0, "Cannot delete pointer to incomplete type"); static_assert(sizeof(T) > 0, "Cannot delete pointer to incomplete type");
delete data; delete data;
@ -636,29 +591,10 @@ template<class T> inline ResourceManagerData<T>::Data::~Data() {
} }
#ifdef MAGNUM_BUILD_DEPRECATED template<class ...Types> ResourceManager<Types...>::ResourceManager() = default;
template<class ...Types> ResourceManager<Types...>& ResourceManager<Types...>::instance() {
CORRADE_ASSERT(Implementation::ResourceManagerImplementation<Types...>::internalInstance(),
"ResourceManager::instance(): no instance exists",
static_cast<ResourceManager<Types...>&>(*Implementation::ResourceManagerImplementation<Types...>::internalInstance()));
return static_cast<ResourceManager<Types...>&>(*Implementation::ResourceManagerImplementation<Types...>::internalInstance());
}
#endif
template<class ...Types> ResourceManager<Types...>::ResourceManager() {
#ifdef MAGNUM_BUILD_DEPRECATED
CORRADE_ASSERT(!Implementation::ResourceManagerImplementation<Types...>::internalInstance(),
"ResourceManager::ResourceManager(): another instance is already created", );
Implementation::ResourceManagerImplementation<Types...>::internalInstance() = this;
#endif
}
template<class ...Types> ResourceManager<Types...>::~ResourceManager() { template<class ...Types> ResourceManager<Types...>::~ResourceManager() {
freeLoaders(typename Implementation::ResourceManagerImplementation<Types...>::TypePack{}); freeLoaders(typename Implementation::ResourceTypePack<Types...>{});
#ifdef MAGNUM_BUILD_DEPRECATED
CORRADE_INTERNAL_ASSERT(Implementation::ResourceManagerImplementation<Types...>::internalInstance() == this);
Implementation::ResourceManagerImplementation<Types...>::internalInstance() = nullptr;
#endif
} }
} }

56
src/Magnum/ResourceManager.hpp

@ -1,56 +0,0 @@
#ifndef Magnum_ResourceManager_hpp
#define Magnum_ResourceManager_hpp
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "ResourceManager.h"
/*
File-local definition of ResourceManager instance holder for use in cases
where the class is used across library boundaries, in which case additional
care must be done to ensure a single static instance.
Usage: typedef the resource manager with Implementation::ResourceManagerLocalInstance
as a first type and then include this file in a _single_ *.cpp file.
This symbol is always exported.
*/
namespace Magnum { namespace Implementation {
#ifdef MAGNUM_BUILD_DEPRECATED
template<class ...Types>
#ifndef _MSC_VER
CORRADE_VISIBILITY_EXPORT
#endif
ResourceManager<Types...>*& ResourceManagerLocalInstanceImplementation<Types...>::internalInstance() {
static ResourceManager<Types...>* _instance(nullptr);
return _instance;
}
#endif
}}
#endif

14
src/Magnum/Test/CMakeLists.txt

@ -48,17 +48,3 @@ set_target_properties(
SamplerTest SamplerTest
TagsTest TagsTest
PROPERTIES FOLDER "Magnum/Test") PROPERTIES FOLDER "Magnum/Test")
if(MAGNUM_BUILD_DEPRECATED)
add_library(ResourceManagerLocalInstanceTestLib ${SHARED_OR_STATIC} ResourceManagerLocalInstanceTestLib.cpp)
target_link_libraries(ResourceManagerLocalInstanceTestLib Magnum)
if(NOT BUILD_STATIC)
target_compile_definitions(ResourceManagerLocalInstanceTestLib PRIVATE "ResourceManagerLocalInstanceTestLib_EXPORTS")
endif()
corrade_add_test(ResourceManagerLocalInstanceTest ResourceManagerLocalInstanceTest.cpp LIBRARIES Magnum ResourceManagerLocalInstanceTestLib)
set_target_properties(
ResourceManagerLocalInstanceTestLib
ResourceManagerLocalInstanceTest
PROPERTIES FOLDER "Magnum/Test")
endif()

55
src/Magnum/Test/ResourceManagerLocalInstanceTest.cpp

@ -1,55 +0,0 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <Corrade/TestSuite/Tester.h>
#include "ResourceManagerLocalInstanceTestLib.h"
namespace Magnum { namespace Test { namespace {
struct ResourceManagerLocalInstanceTest: TestSuite::Tester {
explicit ResourceManagerLocalInstanceTest();
void instance();
ResourceManagerWithLocalInstance manager;
};
ResourceManagerLocalInstanceTest::ResourceManagerLocalInstanceTest() {
addTests({&ResourceManagerLocalInstanceTest::instance});
}
void ResourceManagerLocalInstanceTest::instance() {
CORRADE_IGNORE_DEPRECATED_PUSH
ResourceManagerWithLocalInstance::instance().set("another", 13);
CORRADE_COMPARE(&manager.staticInstance, &manager.instance());
CORRADE_IGNORE_DEPRECATED_POP
CORRADE_COMPARE(manager.count<Int>(), 2);
CORRADE_COMPARE(manager.state<Int>("integer"), ResourceState::Final);
}
}}}
CORRADE_TEST_MAIN(Magnum::Test::ResourceManagerLocalInstanceTest)

45
src/Magnum/Test/ResourceManagerLocalInstanceTestLib.cpp

@ -1,45 +0,0 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "ResourceManagerLocalInstanceTestLib.h"
#include "Magnum/ResourceManager.hpp"
namespace Magnum {
namespace Implementation {
template struct CORRADE_VISIBILITY_EXPORT ResourceManagerLocalInstanceImplementation<ResourceManagerLocalInstance, Int>;
}
namespace Test {
CORRADE_IGNORE_DEPRECATED_PUSH
ResourceManagerWithLocalInstance::ResourceManagerWithLocalInstance(): staticInstance(static_cast<ResourceManagerWithLocalInstance&>(instance())) {
/* Add some stuff */
set("integer", 42);
}
CORRADE_IGNORE_DEPRECATED_POP
}}

50
src/Magnum/Test/ResourceManagerLocalInstanceTestLib.h

@ -1,50 +0,0 @@
#ifndef Magnum_Test_ResourceManagerLocalInstanceTestLib_h
#define Magnum_Test_ResourceManagerLocalInstanceTestLib_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "Magnum/ResourceManager.h"
#ifndef MAGNUM_BUILD_STATIC
#if defined(ResourceManagerLocalInstanceTestLib_EXPORTS)
#define MAGNUM_RESOURCEMANAGERLOCALINSTANCETESTLIB_EXPORT CORRADE_VISIBILITY_EXPORT
#else
#define MAGNUM_RESOURCEMANAGERLOCALINSTANCETESTLIB_EXPORT CORRADE_VISIBILITY_IMPORT
#endif
#else
#define MAGNUM_RESOURCEMANAGERLOCALINSTANCETESTLIB_EXPORT CORRADE_VISIBILITY_STATIC
#endif
namespace Magnum { namespace Test {
struct MAGNUM_RESOURCEMANAGERLOCALINSTANCETESTLIB_EXPORT ResourceManagerWithLocalInstance: ResourceManager<Implementation::ResourceManagerLocalInstance, Int> {
explicit ResourceManagerWithLocalInstance();
ResourceManagerWithLocalInstance& staticInstance;
};
}}
#endif
Loading…
Cancel
Save