diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index a0647b4..081c05c 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -29,10 +29,17 @@ .. py:class:: magnum.trade.ImageData2D - Similarly to :ref:`Image2D`, holds its own data buffer, thus doesn't have - an equivalent to :ref:`ImageView2D.owner`. Implicitly convertible to - :ref:`ImageView2D` / :ref:`MutableImageView2D`, so all APIs consuming image - views work with this type as well. + Implicitly convertible to :ref:`ImageView2D` / :ref:`MutableImageView2D`, + so all APIs consuming image views work with this type as well. + + `Memory ownership and reference counting`_ + ========================================== + + The class can be both owning an non-owning depending on the value of + :ref:`data_flags`. If they contain neither :ref:`DataFlags.OWNED` nor + :ref:`DataFlags.GLOBAL`, the :ref:`owner` property references the object + actually owning the pixel data the image points to. This ensures calling + :py:`del` on the original object will *not* invalidate the data. `Pixel data access`_ ==================== @@ -125,6 +132,16 @@ :TODO: remove this line once m.css stops ignoring first caption on a page + `Memory ownership and reference counting`_ + ========================================== + + The class can be both owning an non-owning depending on the value of + :ref:`index_data_flags` and :ref:`vertex_data_flags`. If they contain + neither :ref:`DataFlags.OWNED` nor :ref:`DataFlags.GLOBAL`, the + :ref:`owner` property references the object actually owning the index and + vertex data the mesh points to. This ensures calling :py:`del` on the + original object will *not* invalidate the data. + `Index and attribute data access`_ ================================== @@ -249,6 +266,15 @@ :TODO: remove this line once m.css stops ignoring first caption on a page + `Memory ownership and reference counting`_ + ========================================== + + The class can be both owning an non-owning depending on the value of + :ref:`data_flags`. If they contain neither :ref:`DataFlags.OWNED` nor + :ref:`DataFlags.GLOBAL`, the :ref:`owner` property references the object + actually owning the data the scene points to. This ensures calling + :py:`del` on the original object will *not* invalidate the data. + `Field data access`_ ==================== diff --git a/src/Magnum/CMakeLists.txt b/src/Magnum/CMakeLists.txt index e5d40a4..bf483e0 100644 --- a/src/Magnum/CMakeLists.txt +++ b/src/Magnum/CMakeLists.txt @@ -87,6 +87,10 @@ if(Magnum_SceneGraph_FOUND) add_subdirectory(SceneGraph) endif() +if(Magnum_Trade_FOUND) + add_subdirectory(Trade) +endif() + if(MAGNUM_BUILD_TESTS) add_subdirectory(Test ${EXCLUDE_FROM_ALL_IF_TEST_TARGET}) endif() diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt new file mode 100644 index 0000000..f6e5159 --- /dev/null +++ b/src/Magnum/Trade/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, +# 2020, 2021, 2022 Vladimír Vondruš +# +# 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. +# + +if(MAGNUM_WITH_PYTHON) + add_custom_target(MagnumTradePython SOURCES PythonBindings.h) + install(FILES PythonBindings.h DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Trade) +endif() diff --git a/src/Magnum/Trade/PythonBindings.h b/src/Magnum/Trade/PythonBindings.h new file mode 100644 index 0000000..eaa15d1 --- /dev/null +++ b/src/Magnum/Trade/PythonBindings.h @@ -0,0 +1,73 @@ +#ifndef Magnum_Trade_PythonBindings_h +#define Magnum_Trade_PythonBindings_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include /* :( */ +#include + +#include "Magnum/Trade/Data.h" +#include "Magnum/Trade/MeshData.h" /* :( */ + +namespace Magnum { namespace Trade { + +namespace Implementation { + +/* For assertions only */ +template inline bool pyDataFlagsNeedOwner(const T& data) { + return !(data.dataFlags() & (DataFlag::Owned|DataFlag::Global)); +} +inline bool pyDataFlagsNeedOwner(const Trade::MeshData& data) { + return + !(data.indexDataFlags() & (DataFlag::Owned|DataFlag::Global)) || + !(data.vertexDataFlags() & (DataFlag::Owned|DataFlag::Global)); +} + +} + +/* Stores additional stuff needed for proper refcounting of non-owning FooData. + Better than subclassing each FooData class because then we would need to + wrap it every time it's exposed to Python, making 3rd party bindings + unnecessarily complex */ +template struct PyDataHolder: std::unique_ptr { + explicit PyDataHolder(T* object): PyDataHolder{object, pybind11::none{}} { + /* Data without an owner can only be self-owned or global */ + CORRADE_INTERNAL_ASSERT(!Implementation::pyDataFlagsNeedOwner(*object)); + } + + explicit PyDataHolder(T* object, pybind11::object owner): std::unique_ptr{object}, owner{std::move(owner)} {} + + pybind11::object owner; +}; + +template PyDataHolder pyDataHolder(T&& data, pybind11::object owner) { + return PyDataHolder{new T{std::move(data)}, std::move(owner)}; +} + +}} + +PYBIND11_DECLARE_HOLDER_TYPE(T, Magnum::Trade::PyDataHolder) + +#endif diff --git a/src/python/magnum/CMakeLists.txt b/src/python/magnum/CMakeLists.txt index d1729b9..5b7208c 100644 --- a/src/python/magnum/CMakeLists.txt +++ b/src/python/magnum/CMakeLists.txt @@ -110,7 +110,9 @@ if(NOT MAGNUM_BUILD_STATIC) if(Magnum_MeshTools_FOUND) pybind11_add_module(magnum_meshtools ${pybind11_add_module_SYSTEM} ${magnum_meshtools_SRCS}) - target_include_directories(magnum_meshtools PRIVATE ${PROJECT_SOURCE_DIR}/src/python) + target_include_directories(magnum_meshtools PRIVATE + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/src/python) target_link_libraries(magnum_meshtools PRIVATE Magnum::MeshTools) set_target_properties(magnum_meshtools PROPERTIES OUTPUT_NAME "meshtools" diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index c2e54e3..ed7c98f 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -46,6 +46,7 @@ class ImageData(unittest.TestCase): self.assertEqual(image.format, PixelFormat.RGB8_UNORM) self.assertEqual(image.pixel_size, 3) self.assertEqual(image.size, Vector2i(3, 2)) + self.assertIsNone(image.owner) def test_compressed(self): # The only way to get an image instance is through a manager @@ -247,6 +248,7 @@ class MeshData(unittest.TestCase): self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(mesh.index_data_flags, trade.DataFlags.OWNED|trade.DataFlags.MUTABLE) self.assertEqual(mesh.vertex_data_flags, trade.DataFlags.OWNED|trade.DataFlags.MUTABLE) + self.assertIsNone(mesh.owner) # Index properties self.assertTrue(mesh.is_indexed) @@ -727,6 +729,7 @@ class SceneData(unittest.TestCase): self.assertEqual(scene.field_size_bound, 4) self.assertFalse(scene.is_2d) self.assertTrue(scene.is_3d) + self.assertIsNone(scene.owner) # Field properties by ID self.assertEqual(scene.field_name(2), trade.SceneField.TRANSFORMATION) diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 7ce12b5..a71e8e5 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -48,6 +48,7 @@ #include "Corrade/Containers/OptionalPythonBindings.h" #include "Magnum/PythonBindings.h" #include "Magnum/StridedArrayViewPythonBindings.h" +#include "Magnum/Trade/PythonBindings.h" #include "corrade/EnumOperators.h" #include "corrade/pluginmanager.h" @@ -186,7 +187,7 @@ template Containers::PyArrayViewHolder{flattenPixelView(data, pixels), formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(image)); } -template void imageData(py::class_>& c) { +template void imageData(py::class_, Trade::PyDataHolder>>& c) { /* Missing APIs: @@ -282,7 +283,11 @@ template void imageData(py::class_& self) { + return pyObjectHolderFor(self).owner; + }, "Memory owner"); } /* For some reason having ...Args as the second (and not last) template @@ -827,7 +832,7 @@ void trade(py::module_& m) { .value("OBJECT_ID", Trade::MeshAttribute::ObjectId); enumWithCustomValues(meshAttribute); - py::class_{m, "MeshData", "Mesh data"} + py::class_>{m, "MeshData", "Mesh data"} .def_property_readonly("primitive", &Trade::MeshData::primitive, "Primitive") .def_property_readonly("index_data_flags", [](Trade::MeshData& self) { return Trade::DataFlag(Containers::enumCastUnderlyingType(self.indexDataFlags())); @@ -1051,11 +1056,15 @@ void trade(py::module_& m) { throw py::error_already_set{}; } return meshAttributeView(self, id, self.mutableAttribute(id)); - }, "Mutable data for given attribute", py::arg("id")); + }, "Mutable data for given attribute", py::arg("id")) + + .def_property_readonly("owner", [](Trade::MeshData& self) { + return pyObjectHolderFor(self).owner; + }, "Memory owner"); - py::class_ imageData1D{m, "ImageData1D", "One-dimensional image data"}; - py::class_ imageData2D{m, "ImageData2D", "Two-dimensional image data"}; - py::class_ imageData3D{m, "ImageData3D", "Three-dimensional image data"}; + py::class_> imageData1D{m, "ImageData1D", "One-dimensional image data"}; + py::class_> imageData2D{m, "ImageData2D", "Two-dimensional image data"}; + py::class_> imageData3D{m, "ImageData3D", "Three-dimensional image data"}; imageData(imageData1D); imageData(imageData2D); imageData(imageData3D); @@ -1198,7 +1207,7 @@ void trade(py::module_& m) { .value("NONE", Trade::SceneFieldFlag{}); corrade::enumOperators(sceneFieldFlag); - py::class_{m, "SceneData", "Scene data"} + py::class_>{m, "SceneData", "Scene data"} .def_property_readonly("data_flags", [](Trade::SceneData& self) { return Trade::DataFlag(Containers::enumCastUnderlyingType(self.dataFlags())); }, "Data flags") @@ -1453,7 +1462,11 @@ void trade(py::module_& m) { throw py::error_already_set{}; } return sceneFieldView(self, id, self.mutableField(id)); - }, "Mutable data for given field", py::arg("name")); + }, "Mutable data for given field", py::arg("name")) + + .def_property_readonly("owner", [](Trade::SceneData& self) { + return pyObjectHolderFor(self).owner; + }, "Memory owner"); py::enum_{m, "TextureType", "Texture type"} .value("TEXTURE1D", Trade::TextureType::Texture1D)