Skip to content

Commit

Permalink
Better handling of c++ integer types
Browse files Browse the repository at this point in the history
  • Loading branch information
kunitoki committed Feb 22, 2024
1 parent 529a894 commit 3b083c7
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 10 deletions.
64 changes: 56 additions & 8 deletions modules/juce_python/bindings/ScriptJuceCoreBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "../utilities/CrashHandling.h"

#include <optional>
#include <string_view>

namespace PYBIND11_NAMESPACE {
Expand Down Expand Up @@ -425,10 +426,10 @@ void registerRange (py::module_& m)

([&]
{
using ValueType = Types;
using ValueType = underlying_type_t<Types>;
using T = Class<ValueType>;

const auto className = popsicle::Helpers::pythonizeCompoundClassName ("Range", typeid (Types).name());
const auto className = popsicle::Helpers::pythonizeCompoundClassName ("Range", typeid (ValueType).name());

auto class_ = py::class_<T> (m, className.toRawUTF8())
.def (py::init<>())
Expand Down Expand Up @@ -462,7 +463,11 @@ void registerRange (py::module_& m)
.def ("getUnionWith", py::overload_cast<T> (&T::getUnionWith, py::const_))
.def ("getUnionWith", py::overload_cast<const ValueType> (&T::getUnionWith, py::const_))
.def ("constrainRange", &T::constrainRange)
//.def_static ("findMinAndMax", &T::template findMinAndMax<int>)
//.def_static ("findMinAndMax", [](const T& self, py::buffer values, int numValues)
//{
// auto info = values.request();
// return self.findMinAndMax (reinterpret_cast<ValueType*> (info.ptr), numValues);
//})
.def ("__repr__", [](const T& self)
{
String result;
Expand All @@ -473,6 +478,9 @@ void registerRange (py::module_& m)
})
;

if constexpr (! std::is_same_v<ValueType, Types>)
class_.def (py::init ([](Types start, Types end) { return T (static_cast<ValueType> (start), static_cast<ValueType> (end)); }));

type[py::type::of (py::cast (Types{}))] = class_;

return true;
Expand All @@ -490,10 +498,10 @@ void registerAtomic (py::module_& m)

([&]
{
using ValueType = Types;
using ValueType = underlying_type_t<Types>;
using T = Class<ValueType>;

const auto className = popsicle::Helpers::pythonizeCompoundClassName ("Atomic", typeid (Types).name());
const auto className = popsicle::Helpers::pythonizeCompoundClassName ("Atomic", typeid (ValueType).name());

auto class_ = py::class_<T> (m, className.toRawUTF8())
.def (py::init<>())
Expand All @@ -506,6 +514,9 @@ void registerAtomic (py::module_& m)
.def ("memoryBarrier", &T::memoryBarrier)
;

if constexpr (! std::is_same_v<ValueType, Types>)
class_.def (py::init ([](Types value) { return T (static_cast<ValueType> (value)); }));

if constexpr (!std::is_same_v<ValueType, bool> && !std::is_floating_point_v<ValueType>)
{
class_
Expand All @@ -528,6 +539,40 @@ void registerJuceCoreBindings (py::module_& m)
juce::SystemStats::setApplicationCrashHandler (Helpers::applicationCrashHandler);
#endif

// ============================================================================================ GenericInteger<T>

py::class_<GenericInteger<int8>> (m, "int8")
.def (py::init<int8>())
.def ("get", &GenericInteger<int8>::get);

py::class_<GenericInteger<uint8>> (m, "uint8")
.def (py::init<uint8>())
.def ("get", &GenericInteger<uint8>::get);

py::class_<GenericInteger<int16>> (m, "int16")
.def (py::init<int16>())
.def ("get", &GenericInteger<int16>::get);

py::class_<GenericInteger<uint16>> (m, "uint16")
.def (py::init<uint16>())
.def ("get", &GenericInteger<uint16>::get);

py::class_<GenericInteger<int32>> (m, "int32")
.def (py::init<int32>())
.def ("get", &GenericInteger<int32>::get);

py::class_<GenericInteger<uint32>> (m, "uint32")
.def (py::init<uint32>())
.def ("get", &GenericInteger<uint32>::get);

py::class_<GenericInteger<int64>> (m, "int64")
.def (py::init<int64>())
.def ("get", &GenericInteger<int64>::get);

py::class_<GenericInteger<uint64>> (m, "uint64")
.def (py::init<uint64>())
.def ("get", &GenericInteger<uint64>::get);

// ============================================================================================ juce::Math

m.def ("juce_hypot", &juce_hypot<float>);
Expand Down Expand Up @@ -1188,7 +1233,7 @@ void registerJuceCoreBindings (py::module_& m)

// ============================================================================================ juce::Range<>

registerRange<Range, int, float> (m);
registerRange<Range, int, GenericInteger<int64>, float> (m);

// ============================================================================================ juce::MemoryBlock

Expand Down Expand Up @@ -1668,9 +1713,12 @@ void registerJuceCoreBindings (py::module_& m)
classMemoryMappedFile
.def (py::init<const File&, MemoryMappedFile::AccessMode, bool>(), "file"_a, "mode"_a, "exclusive"_a = false)
.def (py::init<const File&, const Range<int64>&, MemoryMappedFile::AccessMode, bool>(), "file"_a, "fileRange"_a, "mode"_a, "exclusive"_a = false)
.def ("getData", [](const MemoryMappedFile* self)
.def ("getData", [](const MemoryMappedFile& self) -> std::optional<py::memoryview>
{
return py::memoryview::from_memory (self->getData(), static_cast<Py_ssize_t> (self->getSize()));
if (self.getData() == nullptr)
return std::nullopt;

return py::memoryview::from_memory (self.getData(), static_cast<Py_ssize_t> (self.getSize()));
}, py::return_value_policy::reference_internal)
.def ("getSize", &MemoryMappedFile::getSize)
.def ("getRange", &MemoryMappedFile::getRange)
Expand Down
8 changes: 6 additions & 2 deletions modules/juce_python/bindings/ScriptJuceCoreBindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "../utilities/ClassDemangling.h"
#include "../utilities/PythonInterop.h"
#include "../utilities/PythonTypes.h"

#include <cstddef>
#include <functional>
Expand Down Expand Up @@ -130,10 +131,10 @@ void registerArray (pybind11::module_& m)

([&]
{
using ValueType = Types;
using ValueType = underlying_type_t<Types>;
using T = Class<ValueType, DummyCriticalSection, 0>;

const auto className = popsicle::Helpers::pythonizeCompoundClassName ("Array", typeid (Types).name());
const auto className = popsicle::Helpers::pythonizeCompoundClassName ("Array", typeid (ValueType).name());

py::class_<T> class_ (m, className.toRawUTF8());

Expand Down Expand Up @@ -216,6 +217,9 @@ void registerArray (pybind11::module_& m)
})
;

if constexpr (! std::is_same_v<ValueType, Types>)
class_.def (py::init ([](Types value) { return T (static_cast<ValueType> (value)); }));

if constexpr (isEqualityComparable<ValueType>::value)
{
class_
Expand Down
1 change: 1 addition & 0 deletions modules/juce_python/utilities/ClassDemangling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ juce::String pythonizeClassName (juce::StringRef className, int maxTemplateArgs)
}

return name
.replace ("popsicle::", "")
.replace ("juce::", "")
.replace ("::", ".")
.replace ("<", "[")
Expand Down
78 changes: 78 additions & 0 deletions modules/juce_python/utilities/PythonTypes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* juce_python - Python bindings for the JUCE framework.
*
* This file is part of the popsicle project.
*
* Copyright (c) 2024 - kunitoki <[email protected]>
*
* popsicle is an open source library subject to commercial or open-source licensing.
*
* By using popsicle, you agree to the terms of the popsicle License Agreement, which can
* be found at https://raw.githubusercontent.com/kunitoki/popsicle/master/LICENSE
*
* Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses).
*
* POPSICLE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED
* OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED.
*/

#pragma once

#include <cstdint>
#include <functional>

namespace popsicle {

// =================================================================================================

template <class T>
class GenericInteger
{
public:
using underlying_type = T;

constexpr GenericInteger() = default;

constexpr GenericInteger (T value) noexcept
: value (value)
{
}

constexpr GenericInteger (const GenericInteger& other) = default;
constexpr GenericInteger (GenericInteger&& other) = default;
constexpr GenericInteger& operator= (const GenericInteger& other) = default;
constexpr GenericInteger& operator= (GenericInteger&& other) = default;

constexpr operator T() const noexcept
{
return value;
}

constexpr T get() const noexcept
{
return value;
}

private:
T value{};
};

// =================================================================================================

template <class T>
struct underlying_type
{
using type = T;
};

template <class T>
struct underlying_type<GenericInteger<T>>
{
using type = typename GenericInteger<T>::underlying_type;
};


template <class T>
using underlying_type_t = typename underlying_type<T>::type;

} // namespace popsicle
64 changes: 64 additions & 0 deletions tests/test_juce_core/test_MemoryMappedFile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os

import popsicle as juce
from popsicle import int64

#==================================================================================================

this_file = juce.File(os.path.abspath(__file__))
data_folder = this_file.getSiblingFile("data")

#==================================================================================================

def test_memory_mapped_file_read_only_mode():
mmf = juce.MemoryMappedFile(data_folder.getChildFile("somefile.txt"), juce.MemoryMappedFile.readOnly)
assert mmf.getData() is not None
assert mmf.getSize() > 0
assert mmf.getRange().getStart() >= 0
assert mmf.getRange().getEnd() > mmf.getRange().getStart()

#==================================================================================================

def test_memory_mapped_file_read_write_mode():
mmf = juce.MemoryMappedFile(data_folder.getChildFile("somefile.txt"), juce.MemoryMappedFile.readWrite)
assert mmf.getData() is not None
assert mmf.getSize() > 0
assert mmf.getRange().getStart() >= 0
assert mmf.getRange().getEnd() > mmf.getRange().getStart()

#==================================================================================================

def test_memory_mapped_file_exclusive_mode():
mmf = juce.MemoryMappedFile(data_folder.getChildFile("somefile.txt"), juce.MemoryMappedFile.readOnly, exclusive=True)
assert mmf.getData() is not None
assert mmf.getSize() > 0

#==================================================================================================

def test_memory_mapped_file_non_existent_file():
mmf = juce.MemoryMappedFile(data_folder.getChildFile("nonexistent.txt"), juce.MemoryMappedFile.readOnly)
assert mmf.getData() is None
assert mmf.getSize() == 0

#==================================================================================================

def test_memory_mapped_file_section_read_only_mode():
mmf = juce.MemoryMappedFile(data_folder.getChildFile("somefile.txt"), juce.Range[int64](0, 10), juce.MemoryMappedFile.readOnly)
assert mmf.getData() is not None
assert mmf.getSize() == 10
assert mmf.getRange().getLength() == 10

#==================================================================================================

def test_memory_mapped_file_section_read_write_mode():
mmf = juce.MemoryMappedFile(data_folder.getChildFile("somefile.txt"), juce.Range[int64](0, 10), juce.MemoryMappedFile.readWrite)
assert mmf.getData() is not None
assert mmf.getSize() == 10
assert mmf.getRange().getLength() == 10

#==================================================================================================

def test_memory_mapped_file_section_exclusive_mode():
mmf = juce.MemoryMappedFile(data_folder.getChildFile("somefile.txt"), juce.Range[int64](0, 10), juce.MemoryMappedFile.readOnly, exclusive=True)
assert mmf.getData() is not None
assert mmf.getSize() == 10

0 comments on commit 3b083c7

Please sign in to comment.