Browse Source

python: add an ability for referencing owner of trade.*Data memory.

This is important in case the data aren't owned by the instance but
instead referencing something else, for example the importer, a
memory-mapped file or another instance. Will get increasingly
important for zero-copy data import.
next
Vladimír Vondruš 3 years ago
parent
commit
557277e995
  1. 34
      doc/python/magnum.trade.rst
  2. 4
      src/Magnum/CMakeLists.txt
  3. 29
      src/Magnum/Trade/CMakeLists.txt
  4. 73
      src/Magnum/Trade/PythonBindings.h
  5. 4
      src/python/magnum/CMakeLists.txt
  6. 3
      src/python/magnum/test/test_trade.py
  7. 31
      src/python/magnum/trade.cpp

34
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`_
====================

4
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()

29
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š <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.
#
if(MAGNUM_WITH_PYTHON)
add_custom_target(MagnumTradePython SOURCES PythonBindings.h)
install(FILES PythonBindings.h DESTINATION ${MAGNUM_INCLUDE_INSTALL_DIR}/Trade)
endif()

73
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š <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 <memory> /* :( */
#include <pybind11/pybind11.h>
#include "Magnum/Trade/Data.h"
#include "Magnum/Trade/MeshData.h" /* :( */
namespace Magnum { namespace Trade {
namespace Implementation {
/* For assertions only */
template<class T> 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<class T> struct PyDataHolder: std::unique_ptr<T> {
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<T>{object}, owner{std::move(owner)} {}
pybind11::object owner;
};
template<class T> PyDataHolder<T> pyDataHolder(T&& data, pybind11::object owner) {
return PyDataHolder<T>{new T{std::move(data)}, std::move(owner)};
}
}}
PYBIND11_DECLARE_HOLDER_TYPE(T, Magnum::Trade::PyDataHolder<T>)
#endif

4
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"

3
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)

31
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<UnsignedInt dimensions, class T> Containers::PyArrayViewHolder<Containe
return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<dimensions, T>{flattenPixelView(data, pixels), formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(image));
}
template<UnsignedInt dimensions> void imageData(py::class_<Trade::ImageData<dimensions>>& c) {
template<UnsignedInt dimensions> void imageData(py::class_<Trade::ImageData<dimensions>, Trade::PyDataHolder<Trade::ImageData<dimensions>>>& c) {
/*
Missing APIs:
@ -282,7 +283,11 @@ template<UnsignedInt dimensions> void imageData(py::class_<Trade::ImageData<dime
throw py::error_already_set{};
}
return imagePixelsView(self, self.mutableData(), self.mutablePixels());
}, "Mutable pixel data");
}, "Mutable pixel data")
.def_property_readonly("owner", [](Trade::ImageData<dimensions>& self) {
return pyObjectHolderFor<Trade::PyDataHolder>(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<Trade::MeshAttribute, Trade::Implementation::MeshAttributeCustom>(meshAttribute);
py::class_<Trade::MeshData>{m, "MeshData", "Mesh data"}
py::class_<Trade::MeshData, Trade::PyDataHolder<Trade::MeshData>>{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<Trade::PyDataHolder>(self).owner;
}, "Memory owner");
py::class_<Trade::ImageData1D> imageData1D{m, "ImageData1D", "One-dimensional image data"};
py::class_<Trade::ImageData2D> imageData2D{m, "ImageData2D", "Two-dimensional image data"};
py::class_<Trade::ImageData3D> imageData3D{m, "ImageData3D", "Three-dimensional image data"};
py::class_<Trade::ImageData1D, Trade::PyDataHolder<Trade::ImageData1D>> imageData1D{m, "ImageData1D", "One-dimensional image data"};
py::class_<Trade::ImageData2D, Trade::PyDataHolder<Trade::ImageData2D>> imageData2D{m, "ImageData2D", "Two-dimensional image data"};
py::class_<Trade::ImageData3D, Trade::PyDataHolder<Trade::ImageData3D>> 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_<Trade::SceneData>{m, "SceneData", "Scene data"}
py::class_<Trade::SceneData, Trade::PyDataHolder<Trade::SceneData>>{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<Trade::PyDataHolder>(self).owner;
}, "Memory owner");
py::enum_<Trade::TextureType>{m, "TextureType", "Texture type"}
.value("TEXTURE1D", Trade::TextureType::Texture1D)

Loading…
Cancel
Save