mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-24 07:26:34 +00:00
Change python wrapping of arrays to be more pythonic, by reference
* Previously we would convert from python to C++ arrays immediately by copying, and vice-versa convert TO python immediately by creating a new python list by copying. * This however behaves rather poorly in common situations, e.g.: > foo.bar.append(5) Would not append 5 to foo.bar, but copy foo.bar to a temporary, append 5 to it, then destroy it leaving foo.bar untouched. * Instead we leave the C++ array type as a pointer for as long as we can and instead implement the python sequence API as extensions/slots that work in-place on the original array.
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Baldur Karlsson
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// utility function to get the real C++ this pointer out of PyObject* given
|
||||
// a type, so we can modify it by reference if we want
|
||||
template <typename arrayType>
|
||||
arrayType *array_thisptr(PyObject *self)
|
||||
{
|
||||
void *ptr = NULL;
|
||||
int res = 0;
|
||||
swig_type_info *typeInfo = TypeConversion<arrayType>::GetTypeInfo();
|
||||
|
||||
if(!typeInfo)
|
||||
SWIG_exception_fail(SWIG_RuntimeError, "Internal error fetching type info");
|
||||
|
||||
res = SWIG_ConvertPtr(self, &ptr, typeInfo, 0);
|
||||
if(!SWIG_IsOK(res))
|
||||
SWIG_exception_fail(SWIG_ArgError(res), "Couldn't convert array type");
|
||||
|
||||
return (arrayType *)ptr;
|
||||
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Bit of a hack - use a dispatch struct templated on a constant bool, so
|
||||
// we can do a compile-time check if we're converting 'self' and only invoke
|
||||
// array_thisptr for arrays that we want to be modifying by reference.
|
||||
template <bool isSelf>
|
||||
struct self_dispatch
|
||||
{
|
||||
template <typename arrayType>
|
||||
static arrayType *getthis(PyObject *self)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct self_dispatch<true>
|
||||
{
|
||||
template <typename arrayType>
|
||||
static arrayType *getthis(PyObject *self)
|
||||
{
|
||||
return ::array_thisptr<arrayType>(self);
|
||||
}
|
||||
};
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// templated implementations of array functions for both slots and named
|
||||
// functions in python sequences
|
||||
|
||||
// returns the index into an array, including reverseing negative indices to refer to elements from
|
||||
// the end of the array (-1 = last, -2 = second last, etc).
|
||||
template <typename arrayType>
|
||||
Py_ssize_t array_revindex(arrayType *thisptr, PyObject *index)
|
||||
{
|
||||
Py_ssize_t idx = 0;
|
||||
|
||||
if(!PyIndex_Check(index))
|
||||
SWIG_exception_fail(SWIG_TypeError, "invalid index type");
|
||||
|
||||
idx = PyNumber_AsSsize_t(index, PyExc_IndexError);
|
||||
|
||||
// don't need to fail, it's already been thrown
|
||||
if(idx == -1 && PyErr_Occurred())
|
||||
return PY_SSIZE_T_MIN;
|
||||
|
||||
// if the index is negative, treat it as an offset from the end, with -1 being not last but
|
||||
// next-to-last
|
||||
if(idx < 0)
|
||||
idx = (Py_ssize_t)thisptr->size() + idx;
|
||||
|
||||
return idx;
|
||||
fail:
|
||||
return PY_SSIZE_T_MIN;
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
PyObject *array_repr(arrayType *thisptr)
|
||||
{
|
||||
PyObject *result = NULL;
|
||||
PyObject *list = NULL;
|
||||
|
||||
// make a copy as a python list
|
||||
list = ConvertToPy(*thisptr);
|
||||
|
||||
if(list == NULL)
|
||||
SWIG_exception_fail(SWIG_ValueError, "invalid array");
|
||||
|
||||
// use the python list repr function
|
||||
result = PyObject_Repr(list);
|
||||
Py_DECREF(list);
|
||||
return result;
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
PyObject *array_getitem(arrayType *thisptr, Py_ssize_t idx)
|
||||
{
|
||||
if(idx < 0 || (size_t)idx >= thisptr->size())
|
||||
SWIG_exception_fail(SWIG_IndexError, "list index out of range");
|
||||
|
||||
return ConvertToPy(thisptr->at(idx));
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
int array_setitem(arrayType *thisptr, Py_ssize_t idx, PyObject *val)
|
||||
{
|
||||
int res = SWIG_OK;
|
||||
|
||||
if(idx < 0 || (size_t)idx >= thisptr->size())
|
||||
SWIG_exception_fail(SWIG_IndexError, "list assignment index out of range");
|
||||
|
||||
if(val == NULL)
|
||||
{
|
||||
thisptr->erase(idx);
|
||||
res = SWIG_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
res = ConvertFromPy(val, thisptr->at(idx));
|
||||
}
|
||||
|
||||
if(SWIG_IsOK(res))
|
||||
return 0;
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
Py_ssize_t array_len(arrayType *thisptr)
|
||||
{
|
||||
return thisptr->size();
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
PyObject *array_clear(arrayType *thisptr)
|
||||
{
|
||||
thisptr->clear();
|
||||
return SWIG_Py_Void();
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
PyObject *array_reverse(arrayType *thisptr)
|
||||
{
|
||||
std::reverse(thisptr->begin(), thisptr->end());
|
||||
return SWIG_Py_Void();
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
PyObject *array_copy(arrayType *thisptr)
|
||||
{
|
||||
PyObject *list = PyList_New(0);
|
||||
if(!list)
|
||||
return NULL;
|
||||
|
||||
PyObject *ret = NULL;
|
||||
|
||||
for(size_t i = 0; i < thisptr->size(); i++)
|
||||
{
|
||||
ret = ConvertToPy(thisptr->at(i));
|
||||
|
||||
PyList_Append(list, ret);
|
||||
|
||||
if(!ret)
|
||||
SWIG_exception_fail(SWIG_TypeError, "failed to convert element while copying");
|
||||
}
|
||||
|
||||
return list;
|
||||
fail:
|
||||
if(list)
|
||||
Py_XDECREF(list);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
PyObject *array_sort(arrayType *thisptr, PyObject *key, bool reverse)
|
||||
{
|
||||
// TODO - implement sort
|
||||
return SWIG_Py_Void();
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
PyObject *array_append(arrayType *thisptr, PyObject *value)
|
||||
{
|
||||
typename arrayType::value_type converted;
|
||||
int res = ConvertFromPy(value, converted);
|
||||
|
||||
if(SWIG_IsOK(res))
|
||||
{
|
||||
thisptr->push_back(converted);
|
||||
return SWIG_Py_Void();
|
||||
}
|
||||
|
||||
SWIG_exception_fail(SWIG_ArgError(res), "failed to convert element while appending");
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
PyObject *array_insert(arrayType *thisptr, PyObject *index, PyObject *value)
|
||||
{
|
||||
typename arrayType::value_type converted;
|
||||
int res = 0;
|
||||
Py_ssize_t idx = array_revindex(thisptr, index);
|
||||
|
||||
// if an error occurred an exception has been thrown, just return NULL
|
||||
if(idx == PY_SSIZE_T_MIN)
|
||||
return NULL;
|
||||
|
||||
// insert clamps the index
|
||||
if(idx < 0)
|
||||
idx = 0;
|
||||
if(idx > thisptr->count())
|
||||
idx = thisptr->count();
|
||||
|
||||
res = ConvertFromPy(value, converted);
|
||||
|
||||
if(SWIG_IsOK(res))
|
||||
{
|
||||
thisptr->insert(idx, converted);
|
||||
return SWIG_Py_Void();
|
||||
}
|
||||
|
||||
SWIG_exception_fail(SWIG_ArgError(res), "failed to convert element while inserting");
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
template <typename arrayType>
|
||||
PyObject *array_pop(arrayType *thisptr, PyObject *index)
|
||||
{
|
||||
PyObject *ret = NULL;
|
||||
Py_ssize_t idx = index ? array_revindex(thisptr, index) : thisptr->size() - 1;
|
||||
|
||||
// if an error occurred an exception has been thrown, just return NULL
|
||||
if(idx == PY_SSIZE_T_MIN)
|
||||
return NULL;
|
||||
|
||||
if(idx < 0 || idx > thisptr->count())
|
||||
SWIG_exception_fail(SWIG_IndexError, "pop index out of range");
|
||||
|
||||
if(thisptr->empty())
|
||||
SWIG_exception_fail(SWIG_IndexError, "pop from empty list");
|
||||
|
||||
ret = ConvertToPy(thisptr->at(idx));
|
||||
|
||||
if(!ret)
|
||||
SWIG_exception_fail(SWIG_TypeError, "failed to convert element while popping");
|
||||
|
||||
thisptr->erase(idx);
|
||||
return ret;
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handling for making C++ types act as python lists/tuples, to allow a limited amount of
|
||||
// by-reference access from python
|
||||
//
|
||||
// We do this mostly by adding named methods and slots that modify or access the list in place.
|
||||
|
||||
%header %{
|
||||
// hacky macro to check if two variables are the same by looking at their length & second letter.
|
||||
// Used to identify if we're dealing with 'self' in a typemap or not
|
||||
#define STR_EQ(a, b) (sizeof(#a) == sizeof(#b) && (#a[1] == #b[1]))
|
||||
%}
|
||||
|
||||
// a typemap that allows us to modify 'self' in place, but convert any inputs by value.
|
||||
%define LIST_MODIFY_IN_PLACE_TYPEMAP(ContainerType)
|
||||
|
||||
%typemap(in) ContainerType * (unsigned char tempmem[32], bool wasSelf = false) {
|
||||
using array_type = std::remove_pointer<decltype($1)>::type;
|
||||
|
||||
// don't convert 'self', leave as-is so we can modify by reference. Everything else needs to be
|
||||
// converted/copied to allow passing lists (or any sequence) python objects to a function
|
||||
// expecting a particular C++ array type
|
||||
constexpr bool isSelf = STR_EQ($input, self);
|
||||
wasSelf = isSelf;
|
||||
if(isSelf)
|
||||
{
|
||||
// we use an indirect dispatch class here (see self_dispatch) to avoid the need to instantiate
|
||||
// the conversion template for types we don't care about
|
||||
$1 = self_dispatch<isSelf>::getthis<array_type>(self);
|
||||
}
|
||||
else
|
||||
{
|
||||
// convert the sequence by value using ConvertFromPy
|
||||
static_assert(sizeof(tempmem) >= sizeof(array_type), "not enough temp space for $1_basetype");
|
||||
|
||||
tempalloc($1, tempmem);
|
||||
|
||||
int failIdx = 0;
|
||||
int res = TypeConversion<array_type>::ConvertFromPy($input, indirect($1), &failIdx);
|
||||
|
||||
if(!SWIG_IsOK(res))
|
||||
{
|
||||
if(res == SWIG_TypeError)
|
||||
{
|
||||
SWIG_exception_fail(SWIG_ArgError(res), "in method '$symname' argument $argnum of type '$1_basetype'");
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' argument $argnum of type '$1_basetype', decoding element %d", failIdx);
|
||||
SWIG_exception_fail(SWIG_ArgError(res), convert_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
%typemap(freearg) ContainerType * {
|
||||
// if we converted the sequence by-value, then destroy it again
|
||||
if(!wasSelf$argnum)
|
||||
tempdealloc($1);
|
||||
}
|
||||
|
||||
%enddef // %define LIST_MODIFY_IN_PLACE_TYPEMAP
|
||||
|
||||
// this macro defines the list array class named methods, forwarding to templated implementations
|
||||
%define EXTEND_ARRAY_CLASS_METHODS(Container)
|
||||
%extend Container {
|
||||
// functions with optional parameters need to use kwargs otherwise SWIG wraps them in multiple
|
||||
// overloads
|
||||
%feature("kwargs") pop;
|
||||
%feature("kwargs") sort;
|
||||
|
||||
PyObject *append(PyObject *value)
|
||||
{
|
||||
return array_append($self, value);
|
||||
}
|
||||
|
||||
PyObject *clear()
|
||||
{
|
||||
return array_clear($self);
|
||||
}
|
||||
|
||||
PyObject *insert(PyObject *index, PyObject *value)
|
||||
{
|
||||
return array_insert($self, index, value);
|
||||
}
|
||||
|
||||
PyObject *pop(PyObject *index = NULL)
|
||||
{
|
||||
return array_pop($self, index);
|
||||
}
|
||||
|
||||
PyObject *sort(PyObject *key = NULL, bool reverse = false)
|
||||
{
|
||||
return array_sort($self, key, reverse);
|
||||
}
|
||||
|
||||
PyObject *copy()
|
||||
{
|
||||
return array_copy($self);
|
||||
}
|
||||
|
||||
PyObject *reverse()
|
||||
{
|
||||
return array_reverse($self);
|
||||
}
|
||||
|
||||
/*
|
||||
// named functions to implement for list compatibility
|
||||
index(x[, start[, end]]); // Return zero-based index in the list of the first item whose value is x
|
||||
count(value); // Return the number of times x appears in the list.
|
||||
extend(iterable); // Extend the list by appending all the items from the iterable
|
||||
remove(x); // Remove the first item from the list whose value is x.
|
||||
*/
|
||||
} // %extend Container
|
||||
%enddef // define EXTEND_ARRAY_CLASS_METHODS(Container)
|
||||
|
||||
// Declare the slots that we're going to add - see ARRAY_DEFINE_SLOTS for the definitions
|
||||
%define ARRAY_ADD_SLOTS(array_type, unique_name)
|
||||
|
||||
// define slots
|
||||
%feature("python:tp_repr") array_type STRINGIZE(repr_##unique_name);
|
||||
%feature("python:tp_str") array_type STRINGIZE(repr_##unique_name);
|
||||
%feature("python:sq_item") array_type STRINGIZE(getitem_##unique_name);
|
||||
%feature("python:sq_ass_item") array_type STRINGIZE(setitem_##unique_name);
|
||||
%feature("python:sq_length") array_type STRINGIZE(length_##unique_name);
|
||||
|
||||
// https://docs.python.org/3/library/collections.abc.html
|
||||
// https://docs.python.org/3/library/stdtypes.html#typesseq-common
|
||||
// https://docs.python.org/3/library/stdtypes.html#typesseq-mutable
|
||||
// https://docs.python.org/3/library/stdtypes.html#list
|
||||
|
||||
/*
|
||||
// slots to implement for list compatibility
|
||||
slice retrieve
|
||||
binaryfunc mp_subscript;
|
||||
|
||||
slice set/delete
|
||||
objobjargproc mp_ass_subscript;
|
||||
|
||||
concat/repeat
|
||||
binaryfunc sq_concat;
|
||||
ssizeargfunc sq_repeat;
|
||||
binaryfunc sq_inplace_concat;
|
||||
ssizeargfunc sq_inplace_repeat;
|
||||
*/
|
||||
|
||||
%enddef
|
||||
|
||||
// define C exported wrappers to bind to the slots that forward to templated implementations
|
||||
%define ARRAY_DEFINE_SLOTS(array_type, unique_name)
|
||||
|
||||
// define C-exported wrappers that forward to templated implementations
|
||||
%wrapper %{
|
||||
PyObject *repr_##unique_name(PyObject *self)
|
||||
{
|
||||
array_type *thisptr = array_thisptr<array_type>(self);
|
||||
|
||||
if(!thisptr)
|
||||
return NULL;
|
||||
|
||||
return array_repr(thisptr);
|
||||
}
|
||||
|
||||
PyObject *getitem_##unique_name(PyObject *self, Py_ssize_t idx)
|
||||
{
|
||||
array_type *thisptr = array_thisptr<array_type>(self);
|
||||
|
||||
if(!thisptr)
|
||||
return NULL;
|
||||
|
||||
return array_getitem(thisptr, idx);
|
||||
}
|
||||
|
||||
int setitem_##unique_name(PyObject *self, Py_ssize_t idx, PyObject *val)
|
||||
{
|
||||
array_type *thisptr = array_thisptr<array_type>(self);
|
||||
|
||||
if(!thisptr)
|
||||
return -1;
|
||||
|
||||
return array_setitem(thisptr, idx, val);
|
||||
}
|
||||
|
||||
int length_##unique_name(PyObject *self, Py_ssize_t idx, PyObject *val)
|
||||
{
|
||||
array_type *thisptr = array_thisptr<array_type>(self);
|
||||
|
||||
if(!thisptr)
|
||||
return -1;
|
||||
|
||||
return array_len(thisptr);
|
||||
}
|
||||
%}
|
||||
|
||||
%enddef
|
||||
|
||||
%define ARRAY_INSTANTIATION_CHECK_NAME(typeName) add_your_use_of_##typeName##_to_swig_interface %enddef
|
||||
|
||||
// these pair of macros (TEMPLATE_ARRAY_DECLARE / NON_TEMPLATE_ARRAY_INSTANTIATE) are defined before
|
||||
// including the header that defines a class, to set up typemaps and other things that must be
|
||||
// available before the definition is parsed.
|
||||
//
|
||||
// Afterwards, you must call EXTEND_ARRAY_CLASS_METHODS to add the necessary named methods.
|
||||
|
||||
%define TEMPLATE_ARRAY_DECLARE(typeName)
|
||||
|
||||
LIST_MODIFY_IN_PLACE_TYPEMAP(typeName)
|
||||
|
||||
// add a check to make sure that we explicitly instantiate all uses of this template (as a reference
|
||||
// type, not as a purely in parameter to a function - those are converted by value to C++ to allow
|
||||
// passing pure lists that aren't C++ side at all).
|
||||
%header %{
|
||||
template<typename innerType>
|
||||
void ARRAY_INSTANTIATION_CHECK_NAME(typeName)(typeName<innerType> *)
|
||||
{
|
||||
// TODO remove this to make it an error to miss the instantiation of this array type
|
||||
}
|
||||
%}
|
||||
|
||||
// override these typemaps to instantiate a checking template.
|
||||
%typemap(check) typeName * { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); }
|
||||
%typemap(check) typeName & { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); }
|
||||
%typemap(check) typeName { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); }
|
||||
%typemap(ret) typeName * { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); }
|
||||
%typemap(ret) typeName & { ARRAY_INSTANTIATION_CHECK_NAME(typeName)($1); }
|
||||
%typemap(ret) typeName { ARRAY_INSTANTIATION_CHECK_NAME(typeName)(&$1); }
|
||||
|
||||
%enddef
|
||||
|
||||
%define NON_TEMPLATE_ARRAY_INSTANTIATE(typeName)
|
||||
|
||||
ARRAY_ADD_SLOTS(typeName, typeName)
|
||||
ARRAY_DEFINE_SLOTS(typeName, typeName)
|
||||
|
||||
LIST_MODIFY_IN_PLACE_TYPEMAP(typeName)
|
||||
|
||||
%enddef
|
||||
|
||||
// this instantiates a templated array for a particular type and sets it up to act like a python
|
||||
// sequence.
|
||||
%define TEMPLATE_ARRAY_INSTANTIATE(arrayType, innerType)
|
||||
|
||||
ARRAY_ADD_SLOTS(arrayType<innerType>, arrayType##_of_##innerType)
|
||||
|
||||
// instantiate template
|
||||
%rename(arrayType##_of_##innerType) arrayType<innerType>;
|
||||
%template(arrayType##_of_##innerType) arrayType<innerType>;
|
||||
|
||||
ARRAY_DEFINE_SLOTS(arrayType<innerType>, arrayType##_of_##innerType)
|
||||
|
||||
%header %{
|
||||
|
||||
template<>
|
||||
void ARRAY_INSTANTIATION_CHECK_NAME(arrayType)(arrayType<innerType> *)
|
||||
{
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%enddef
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
// add some useful builtin functions for ResourceId
|
||||
%feature("python:tp_str") ResourceId "resid_str";
|
||||
%feature("python:tp_repr") ResourceId "resid_str";
|
||||
%feature("python:nb_int") ResourceId "resid_int";
|
||||
|
||||
%wrapper %{
|
||||
static PyObject *resid_str(PyObject *resid)
|
||||
{
|
||||
void *resptr = NULL;
|
||||
unsigned long long *id = NULL;
|
||||
int res = SWIG_ConvertPtr(resid, &resptr, SWIGTYPE_p_ResourceId, 0);
|
||||
if (!SWIG_IsOK(res)) {
|
||||
SWIG_exception_fail(SWIG_ArgError(res), "in method 'ResourceId.str', ResourceId is not correct type");
|
||||
}
|
||||
|
||||
// cast as unsigned long long
|
||||
id = (unsigned long long *)resptr;
|
||||
static_assert(sizeof(unsigned long long) == sizeof(ResourceId), "Wrong size");
|
||||
|
||||
return PyUnicode_FromFormat("<ResourceId %llu>", *id);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject *resid_int(PyObject *resid)
|
||||
{
|
||||
void *resptr = NULL;
|
||||
unsigned long long *id = NULL;
|
||||
int res = SWIG_ConvertPtr(resid, &resptr, SWIGTYPE_p_ResourceId, 0);
|
||||
if (!SWIG_IsOK(res)) {
|
||||
SWIG_exception_fail(SWIG_ArgError(res), "in method 'ResourceId.str', ResourceId is not correct type");
|
||||
}
|
||||
|
||||
// cast as unsigned long long
|
||||
id = (unsigned long long *)resptr;
|
||||
static_assert(sizeof(unsigned long long) == sizeof(ResourceId), "Wrong size");
|
||||
|
||||
return PyLong_FromUnsignedLongLong(*id);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
%}
|
||||
@@ -0,0 +1,215 @@
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Baldur Karlsson
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
// this is defined elsewhere for managing the opaque global_handle object
|
||||
extern "C" PyThreadState *GetExecutingThreadState(PyObject *global_handle);
|
||||
extern "C" void HandleException(PyObject *global_handle);
|
||||
extern "C" bool IsThreadBlocking(PyObject *global_handle);
|
||||
extern "C" void SetThreadBlocking(PyObject *global_handle, bool block);
|
||||
|
||||
struct ExceptionHandling
|
||||
{
|
||||
bool failFlag = false;
|
||||
PyObject *exObj = NULL;
|
||||
PyObject *valueObj = NULL;
|
||||
PyObject *tracebackObj = NULL;
|
||||
};
|
||||
|
||||
// this function handles failures in callback functions. If we're synchronously calling the callback
|
||||
// from within an execute scope, then we can assign to failflag and let the error propagate upwards.
|
||||
// If we're not, then the callback is being executed on another thread with no knowledge of python,
|
||||
// so we need to use the global handle to try and emit the exception through the context. None of
|
||||
// this is multi-threaded because we're inside the GIL at all times
|
||||
inline void HandleCallbackFailure(PyObject *global_handle, ExceptionHandling &exHandle)
|
||||
{
|
||||
// if there's no global handle assume we are not running in the usual environment, so there are no
|
||||
// external-to-python threads
|
||||
if(!global_handle)
|
||||
{
|
||||
exHandle.failFlag = true;
|
||||
return;
|
||||
}
|
||||
|
||||
PyThreadState *current = PyGILState_GetThisThreadState();
|
||||
PyThreadState *executing = GetExecutingThreadState(global_handle);
|
||||
|
||||
// we are executing synchronously, set the flag and return
|
||||
if(current == executing)
|
||||
{
|
||||
exHandle.failFlag = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have the blocking flag set, then we may be on another thread but we can still propagate
|
||||
// up the error
|
||||
if(IsThreadBlocking(global_handle))
|
||||
{
|
||||
exHandle.failFlag = true;
|
||||
|
||||
// we need to rethrow the exception to that thread, so fetch (and clear it) on this thread.
|
||||
//
|
||||
// Note that the exception can only propagate up to one place. However since we know that python
|
||||
// is inherently single threaded, so if we're doing this blocking funciton call on another
|
||||
// thread then we *know* there isn't python further up the stack. Therefore we're safe to
|
||||
// swallow the exception here (since there's nowhere for it to bubble up to anyway) and rethrow
|
||||
// on the python thread.
|
||||
PyErr_Fetch(&exHandle.exObj, &exHandle.valueObj, &exHandle.tracebackObj);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// in this case we are executing asynchronously, and must handle the exception manually as there's
|
||||
// nothing above us that knows about python exceptions
|
||||
HandleException(global_handle);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T get_return(const char *funcname, PyObject *result, PyObject *global_handle,
|
||||
ExceptionHandling &exHandle)
|
||||
{
|
||||
T val = T();
|
||||
|
||||
int res = ConvertToPy(result, val);
|
||||
|
||||
if(!SWIG_IsOK(res))
|
||||
{
|
||||
HandleCallbackFailure(global_handle, exHandle);
|
||||
|
||||
PyErr_Format(PyExc_TypeError, "Expected a '%s' for return value of callback in %s",
|
||||
TypeName<T>(), funcname);
|
||||
}
|
||||
|
||||
Py_XDECREF(result);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void get_return(const char *funcname, PyObject *result, PyObject *global_handle,
|
||||
ExceptionHandling &exHandle)
|
||||
{
|
||||
Py_XDECREF(result);
|
||||
}
|
||||
|
||||
template <typename rettype, typename... paramTypes>
|
||||
struct varfunc
|
||||
{
|
||||
varfunc(const char *funcname, paramTypes... params)
|
||||
{
|
||||
args = PyTuple_New(sizeof...(paramTypes));
|
||||
|
||||
currentarg = 0;
|
||||
|
||||
// avoid unused parameter errors when calling a parameter-less function
|
||||
(void)funcname;
|
||||
|
||||
using expand_type = int[];
|
||||
(void)expand_type{0, (push_arg(funcname, params), 0)...};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void push_arg(const char *funcname, const T &arg)
|
||||
{
|
||||
if(!args)
|
||||
return;
|
||||
|
||||
PyObject *obj = ConvertToPy(arg);
|
||||
|
||||
if(!obj)
|
||||
{
|
||||
Py_DecRef(args);
|
||||
args = NULL;
|
||||
|
||||
PyErr_Format(PyExc_TypeError, "Unexpected type for arg %d of callback in %s", currentarg + 1,
|
||||
funcname);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
PyTuple_SetItem(args, currentarg++, obj);
|
||||
}
|
||||
|
||||
~varfunc() { Py_XDECREF(args); }
|
||||
rettype call(const char *funcname, PyObject *func, PyObject *global_handle,
|
||||
ExceptionHandling &exHandle)
|
||||
{
|
||||
if(!func || func == Py_None || !PyCallable_Check(func) || !args)
|
||||
{
|
||||
HandleCallbackFailure(global_handle, exHandle);
|
||||
return rettype();
|
||||
}
|
||||
|
||||
PyObject *result = PyObject_Call(func, args, 0);
|
||||
|
||||
if(result == NULL)
|
||||
HandleCallbackFailure(global_handle, exHandle);
|
||||
|
||||
Py_DECREF(args);
|
||||
|
||||
return get_return<rettype>(funcname, result, global_handle, exHandle);
|
||||
}
|
||||
|
||||
int currentarg = 0;
|
||||
PyObject *args;
|
||||
};
|
||||
|
||||
struct ScopedFuncCall
|
||||
{
|
||||
ScopedFuncCall(PyObject *h)
|
||||
{
|
||||
handle = h;
|
||||
Py_XINCREF(handle);
|
||||
gil = PyGILState_Ensure();
|
||||
}
|
||||
|
||||
~ScopedFuncCall()
|
||||
{
|
||||
Py_XDECREF(handle);
|
||||
PyGILState_Release(gil);
|
||||
}
|
||||
|
||||
PyObject *handle;
|
||||
PyGILState_STATE gil;
|
||||
};
|
||||
|
||||
template <typename funcType>
|
||||
funcType ConvertFunc(const char *funcname, PyObject *func, ExceptionHandling &exHandle)
|
||||
{
|
||||
// add a reference to the global object so it stays alive while we execute, in case this is an
|
||||
// async call
|
||||
PyObject *global_internal_handle = NULL;
|
||||
|
||||
PyObject *globals = PyEval_GetGlobals();
|
||||
if(globals)
|
||||
global_internal_handle = PyDict_GetItemString(globals, "_renderdoc_internal");
|
||||
|
||||
return [global_internal_handle, funcname, func, &exHandle](auto... param) {
|
||||
ScopedFuncCall gil(global_internal_handle);
|
||||
|
||||
varfunc<typename funcType::result_type, decltype(param)...> f(funcname, param...);
|
||||
return f.call(funcname, func, global_internal_handle, exHandle);
|
||||
};
|
||||
}
|
||||
@@ -24,8 +24,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
|
||||
// struct to allow partial specialisation for enums
|
||||
template <typename T, bool isEnum = std::is_enum<T>::value>
|
||||
struct TypeConversion
|
||||
@@ -458,6 +458,8 @@ struct TypeConversion<rdcarray<U>, false>
|
||||
if(elem)
|
||||
{
|
||||
PyList_Append(list, elem);
|
||||
// release our reference
|
||||
Py_DecRef(elem);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -551,294 +553,7 @@ struct TypeConversion<rdcstr, false>
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef ENABLE_QT_CONVERT
|
||||
|
||||
template <>
|
||||
struct TypeConversion<QString, false>
|
||||
{
|
||||
static int ConvertFromPy(PyObject *in, QString &out)
|
||||
{
|
||||
if(PyUnicode_Check(in))
|
||||
{
|
||||
PyObject *bytes = PyUnicode_AsUTF8String(in);
|
||||
|
||||
if(!bytes)
|
||||
return SWIG_ERROR;
|
||||
|
||||
char *buf = NULL;
|
||||
Py_ssize_t size = 0;
|
||||
|
||||
int ret = PyBytes_AsStringAndSize(bytes, &buf, &size);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
out = QString::fromUtf8(buf, (int)size);
|
||||
|
||||
Py_DecRef(bytes);
|
||||
|
||||
return SWIG_OK;
|
||||
}
|
||||
|
||||
Py_DecRef(bytes);
|
||||
|
||||
return SWIG_ERROR;
|
||||
}
|
||||
|
||||
return SWIG_ERROR;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QString &in)
|
||||
{
|
||||
QByteArray bytes = in.toUtf8();
|
||||
return PyUnicode_FromStringAndSize(bytes.data(), bytes.size());
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TypeConversion<QDateTime, false>
|
||||
{
|
||||
static int ConvertFromPy(PyObject *in, QDateTime &out)
|
||||
{
|
||||
if(!PyDateTime_Check(in))
|
||||
return SWIG_TypeError;
|
||||
|
||||
QDate date(PyDateTime_GET_YEAR(in), PyDateTime_GET_MONTH(in), PyDateTime_GET_DAY(in));
|
||||
QTime time(PyDateTime_DATE_GET_HOUR(in), PyDateTime_DATE_GET_MINUTE(in),
|
||||
PyDateTime_DATE_GET_SECOND(in), PyDateTime_DATE_GET_MICROSECOND(in) / 1000);
|
||||
|
||||
out = QDateTime(date, time, QTimeZone::utc());
|
||||
|
||||
return SWIG_OK;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QDateTime &in)
|
||||
{
|
||||
QDate date = in.date();
|
||||
QTime time = in.time();
|
||||
return PyDateTime_FromDateAndTime(date.year(), date.month(), date.day(), time.hour(),
|
||||
time.minute(), time.second(), time.msec() * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Container, typename U>
|
||||
struct ContainerConversion
|
||||
{
|
||||
// we add some extra parameters so the typemaps for array can use these to get
|
||||
// nicer failure error messages out with the index that failed
|
||||
static int ConvertFromPy(PyObject *in, Container &out, int *failIdx)
|
||||
{
|
||||
if(!PyList_Check(in))
|
||||
return SWIG_TypeError;
|
||||
|
||||
Py_ssize_t len = PyList_Size(in);
|
||||
|
||||
for(Py_ssize_t i = 0; i < len; i++)
|
||||
{
|
||||
U u;
|
||||
int ret = TypeConversion<U>::ConvertFromPy(PyList_GetItem(in, i), u);
|
||||
if(!SWIG_IsOK(ret))
|
||||
{
|
||||
if(failIdx)
|
||||
*failIdx = i;
|
||||
return ret;
|
||||
}
|
||||
out.append(u);
|
||||
}
|
||||
|
||||
return SWIG_OK;
|
||||
}
|
||||
|
||||
static int ConvertFromPy(PyObject *in, Container &out) { return ConvertFromPy(in, out, NULL); }
|
||||
static PyObject *ConvertToPyInPlace(PyObject *list, const Container &in, int *failIdx)
|
||||
{
|
||||
for(int i = 0; i < in.size(); i++)
|
||||
{
|
||||
PyObject *elem = TypeConversion<U>::ConvertToPy(in[i]);
|
||||
|
||||
if(elem)
|
||||
{
|
||||
PyList_Append(list, elem);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(failIdx)
|
||||
*failIdx = i;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const Container &in, int *failIdx)
|
||||
{
|
||||
PyObject *list = PyList_New(0);
|
||||
if(!list)
|
||||
return NULL;
|
||||
|
||||
PyObject *ret = ConvertToPyInPlace(list, in, failIdx);
|
||||
|
||||
// if a failure happened, don't leak the list we created
|
||||
if(!ret)
|
||||
Py_XDECREF(list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const Container &in) { return ConvertToPy(in, NULL); }
|
||||
};
|
||||
|
||||
template <typename U>
|
||||
struct TypeConversion<QList<U>, false> : ContainerConversion<QList<U>, U>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TypeConversion<QStringList, false> : ContainerConversion<QList<QString>, QString>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename U>
|
||||
struct TypeConversion<QVector<U>, false> : ContainerConversion<QVector<U>, U>
|
||||
{
|
||||
};
|
||||
|
||||
// specialisation for pair
|
||||
template <typename A, typename B>
|
||||
struct TypeConversion<QPair<A, B>, false>
|
||||
{
|
||||
static int ConvertFromPy(PyObject *in, QPair<A, B> &out)
|
||||
{
|
||||
if(!PyTuple_Check(in))
|
||||
return SWIG_TypeError;
|
||||
|
||||
Py_ssize_t size = PyTuple_Size(in);
|
||||
|
||||
if(size != 2)
|
||||
return SWIG_TypeError;
|
||||
|
||||
int ret = TypeConversion<A>::ConvertFromPy(PyTuple_GetItem(in, 0), out.first);
|
||||
if(SWIG_IsOK(ret))
|
||||
ret = TypeConversion<B>::ConvertFromPy(PyTuple_GetItem(in, 1), out.second);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QPair<A, B> &in)
|
||||
{
|
||||
PyObject *first = TypeConversion<A>::ConvertToPy(in.first);
|
||||
if(!first)
|
||||
return NULL;
|
||||
|
||||
PyObject *second = TypeConversion<B>::ConvertToPy(in.second);
|
||||
if(!second)
|
||||
return NULL;
|
||||
|
||||
PyObject *ret = PyTuple_New(2);
|
||||
if(!ret)
|
||||
return NULL;
|
||||
|
||||
PyTuple_SetItem(ret, 0, first);
|
||||
PyTuple_SetItem(ret, 1, second);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename K, typename V>
|
||||
struct TypeConversion<QMap<K, V>, false>
|
||||
{
|
||||
// we add some extra parameters so the typemaps for array can use these to get
|
||||
// nicer failure error messages out with the index that failed
|
||||
static int ConvertFromPy(PyObject *in, QMap<K, V> &out, int *failIdx)
|
||||
{
|
||||
if(!PyDict_Check(in))
|
||||
return SWIG_TypeError;
|
||||
|
||||
PyObject *keys = PyDict_Keys(in);
|
||||
|
||||
if(!keys)
|
||||
return SWIG_TypeError;
|
||||
|
||||
Py_ssize_t len = PyList_Size(keys);
|
||||
|
||||
for(Py_ssize_t i = 0; i < len; i++)
|
||||
{
|
||||
K k;
|
||||
V v;
|
||||
|
||||
PyObject *key = PyList_GetItem(keys, i);
|
||||
PyObject *value = PyDict_GetItem(in, key);
|
||||
int ret = TypeConversion<K>::ConvertFromPy(key, k);
|
||||
int ret2 = TypeConversion<V>::ConvertFromPy(value, v);
|
||||
if(!SWIG_IsOK(ret) || !SWIG_IsOK(ret2))
|
||||
{
|
||||
if(failIdx)
|
||||
*failIdx = i;
|
||||
Py_DecRef(keys);
|
||||
if(!SWIG_IsOK(ret))
|
||||
return ret;
|
||||
else
|
||||
return ret2;
|
||||
}
|
||||
out.insert(k, v);
|
||||
}
|
||||
|
||||
Py_DecRef(keys);
|
||||
|
||||
return SWIG_OK;
|
||||
}
|
||||
|
||||
static int ConvertFromPy(PyObject *in, QMap<K, V> &out) { return ConvertFromPy(in, out, NULL); }
|
||||
static PyObject *ConvertToPyInPlace(PyObject *pymap, const QMap<K, V> &in, int *failIdx)
|
||||
{
|
||||
QList<K> keys = in.keys();
|
||||
|
||||
for(int i = 0; i < keys.size(); i++)
|
||||
{
|
||||
const K &k = keys[i];
|
||||
PyObject *key = TypeConversion<K>::ConvertToPy(k);
|
||||
|
||||
if(key)
|
||||
{
|
||||
PyObject *value = TypeConversion<V>::ConvertToPy(in[k]);
|
||||
|
||||
if(value)
|
||||
{
|
||||
PyDict_SetItem(pymap, key, value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(failIdx)
|
||||
*failIdx = i;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pymap;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QMap<K, V> &in, int *failIdx)
|
||||
{
|
||||
PyObject *list = PyDict_New();
|
||||
if(!list)
|
||||
return NULL;
|
||||
|
||||
PyObject *ret = ConvertToPyInPlace(list, in, failIdx);
|
||||
|
||||
// if a failure happened, don't leak the map we created
|
||||
if(!ret)
|
||||
Py_XDECREF(list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QMap<K, V> &in) { return ConvertToPy(in, NULL); }
|
||||
};
|
||||
|
||||
#endif
|
||||
#include "qt_conversion.h"
|
||||
|
||||
// free functions forward to struct
|
||||
template <typename T>
|
||||
@@ -853,196 +568,6 @@ PyObject *ConvertToPy(const T &in)
|
||||
return TypeConversion<T>::ConvertToPy(in);
|
||||
}
|
||||
|
||||
// this is defined elsewhere for managing the opaque global_handle object
|
||||
extern "C" PyThreadState *GetExecutingThreadState(PyObject *global_handle);
|
||||
extern "C" void HandleException(PyObject *global_handle);
|
||||
extern "C" bool IsThreadBlocking(PyObject *global_handle);
|
||||
extern "C" void SetThreadBlocking(PyObject *global_handle, bool block);
|
||||
|
||||
struct ExceptionHandling
|
||||
{
|
||||
bool failFlag = false;
|
||||
PyObject *exObj = NULL;
|
||||
PyObject *valueObj = NULL;
|
||||
PyObject *tracebackObj = NULL;
|
||||
};
|
||||
|
||||
// this function handles failures in callback functions. If we're synchronously calling the callback
|
||||
// from within an execute scope, then we can assign to failflag and let the error propagate upwards.
|
||||
// If we're not, then the callback is being executed on another thread with no knowledge of python,
|
||||
// so we need to use the global handle to try and emit the exception through the context. None of
|
||||
// this is multi-threaded because we're inside the GIL at all times
|
||||
inline void HandleCallbackFailure(PyObject *global_handle, ExceptionHandling &exHandle)
|
||||
{
|
||||
// if there's no global handle assume we are not running in the usual environment, so there are no
|
||||
// external-to-python threads
|
||||
if(!global_handle)
|
||||
{
|
||||
exHandle.failFlag = true;
|
||||
return;
|
||||
}
|
||||
|
||||
PyThreadState *current = PyGILState_GetThisThreadState();
|
||||
PyThreadState *executing = GetExecutingThreadState(global_handle);
|
||||
|
||||
// we are executing synchronously, set the flag and return
|
||||
if(current == executing)
|
||||
{
|
||||
exHandle.failFlag = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have the blocking flag set, then we may be on another thread but we can still propagate
|
||||
// up the error
|
||||
if(IsThreadBlocking(global_handle))
|
||||
{
|
||||
exHandle.failFlag = true;
|
||||
|
||||
// we need to rethrow the exception to that thread, so fetch (and clear it) on this thread.
|
||||
//
|
||||
// Note that the exception can only propagate up to one place. However since we know that python
|
||||
// is inherently single threaded, so if we're doing this blocking funciton call on another
|
||||
// thread then we *know* there isn't python further up the stack. Therefore we're safe to
|
||||
// swallow the exception here (since there's nowhere for it to bubble up to anyway) and rethrow
|
||||
// on the python thread.
|
||||
PyErr_Fetch(&exHandle.exObj, &exHandle.valueObj, &exHandle.tracebackObj);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// in this case we are executing asynchronously, and must handle the exception manually as there's
|
||||
// nothing above us that knows about python exceptions
|
||||
HandleException(global_handle);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T get_return(const char *funcname, PyObject *result, PyObject *global_handle,
|
||||
ExceptionHandling &exHandle)
|
||||
{
|
||||
T val = T();
|
||||
|
||||
int res = ConvertToPy(result, val);
|
||||
|
||||
if(!SWIG_IsOK(res))
|
||||
{
|
||||
HandleCallbackFailure(global_handle, exHandle);
|
||||
|
||||
PyErr_Format(PyExc_TypeError, "Expected a '%s' for return value of callback in %s",
|
||||
TypeName<T>(), funcname);
|
||||
}
|
||||
|
||||
Py_XDECREF(result);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void get_return(const char *funcname, PyObject *result, PyObject *global_handle,
|
||||
ExceptionHandling &exHandle)
|
||||
{
|
||||
Py_XDECREF(result);
|
||||
}
|
||||
|
||||
template <typename rettype, typename... paramTypes>
|
||||
struct varfunc
|
||||
{
|
||||
varfunc(const char *funcname, paramTypes... params)
|
||||
{
|
||||
args = PyTuple_New(sizeof...(paramTypes));
|
||||
|
||||
currentarg = 0;
|
||||
|
||||
// avoid unused parameter errors when calling a parameter-less function
|
||||
(void)funcname;
|
||||
|
||||
using expand_type = int[];
|
||||
(void)expand_type{0, (push_arg(funcname, params), 0)...};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void push_arg(const char *funcname, const T &arg)
|
||||
{
|
||||
if(!args)
|
||||
return;
|
||||
|
||||
PyObject *obj = ConvertToPy(arg);
|
||||
|
||||
if(!obj)
|
||||
{
|
||||
Py_DecRef(args);
|
||||
args = NULL;
|
||||
|
||||
PyErr_Format(PyExc_TypeError, "Unexpected type for arg %d of callback in %s", currentarg + 1,
|
||||
funcname);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
PyTuple_SetItem(args, currentarg++, obj);
|
||||
}
|
||||
|
||||
~varfunc() { Py_XDECREF(args); }
|
||||
rettype call(const char *funcname, PyObject *func, PyObject *global_handle,
|
||||
ExceptionHandling &exHandle)
|
||||
{
|
||||
if(!func || func == Py_None || !PyCallable_Check(func) || !args)
|
||||
{
|
||||
HandleCallbackFailure(global_handle, exHandle);
|
||||
return rettype();
|
||||
}
|
||||
|
||||
PyObject *result = PyObject_Call(func, args, 0);
|
||||
|
||||
if(result == NULL)
|
||||
HandleCallbackFailure(global_handle, exHandle);
|
||||
|
||||
Py_DECREF(args);
|
||||
|
||||
return get_return<rettype>(funcname, result, global_handle, exHandle);
|
||||
}
|
||||
|
||||
int currentarg = 0;
|
||||
PyObject *args;
|
||||
};
|
||||
|
||||
struct ScopedFuncCall
|
||||
{
|
||||
ScopedFuncCall(PyObject *h)
|
||||
{
|
||||
handle = h;
|
||||
Py_XINCREF(handle);
|
||||
gil = PyGILState_Ensure();
|
||||
}
|
||||
|
||||
~ScopedFuncCall()
|
||||
{
|
||||
Py_XDECREF(handle);
|
||||
PyGILState_Release(gil);
|
||||
}
|
||||
|
||||
PyObject *handle;
|
||||
PyGILState_STATE gil;
|
||||
};
|
||||
|
||||
template <typename funcType>
|
||||
funcType ConvertFunc(const char *funcname, PyObject *func, ExceptionHandling &exHandle)
|
||||
{
|
||||
// add a reference to the global object so it stays alive while we execute, in case this is an
|
||||
// async call
|
||||
PyObject *global_internal_handle = NULL;
|
||||
|
||||
PyObject *globals = PyEval_GetGlobals();
|
||||
if(globals)
|
||||
global_internal_handle = PyDict_GetItemString(globals, "_renderdoc_internal");
|
||||
|
||||
return [global_internal_handle, funcname, func, &exHandle](auto... param) {
|
||||
ScopedFuncCall gil(global_internal_handle);
|
||||
|
||||
varfunc<typename funcType::result_type, decltype(param)...> f(funcname, param...);
|
||||
return f.call(funcname, func, global_internal_handle, exHandle);
|
||||
};
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T, bool is_pointer = std::is_pointer<T>::value>
|
||||
@@ -1096,3 +621,7 @@ inline typename std::remove_pointer<T>::type &indirect(T &ptr)
|
||||
{
|
||||
return pointer_unwrap<T>::indirect(ptr);
|
||||
}
|
||||
|
||||
#include "function_conversion.h"
|
||||
|
||||
#include "container_handling.h"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// this file is included from renderdoc.i, it's not a module in itself
|
||||
|
||||
%define STRINGIZE(val) #val %enddef
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// typemaps for more sensible fixed-array handling, based on typemaps from SWIG documentation
|
||||
|
||||
%define FIXED_ARRAY_TYPEMAPS(BaseType)
|
||||
@@ -50,8 +53,12 @@
|
||||
|
||||
%enddef
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// simple typemaps for an object that's converted directly by-value. This is perfect for python
|
||||
// immutable objects like strings or datetimes
|
||||
|
||||
%define SIMPLE_TYPEMAPS_VARIANT(BaseType, SimpleType)
|
||||
%typemap(in, fragment="pyconvert") SimpleType (BaseType temp) {
|
||||
%typemap(in) SimpleType (BaseType temp) {
|
||||
tempset($1, &temp);
|
||||
|
||||
int res = ConvertFromPy($input, indirect($1));
|
||||
@@ -61,8 +68,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
%typemap(out, fragment="pyconvert") SimpleType {
|
||||
$result = ConvertToPy( indirect($1));
|
||||
%typemap(out) SimpleType {
|
||||
$result = ConvertToPy(indirect($1));
|
||||
}
|
||||
%enddef
|
||||
|
||||
@@ -74,50 +81,44 @@ SIMPLE_TYPEMAPS_VARIANT(SimpleType, SimpleType &)
|
||||
|
||||
%enddef
|
||||
|
||||
%define CONTAINER_TYPEMAPS_VARIANT(ContainerType)
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// typemaps for std::function
|
||||
|
||||
%typemap(in, fragment="pyconvert") ContainerType (unsigned char tempmem[32]) {
|
||||
static_assert(sizeof(tempmem) >= sizeof(std::remove_pointer<decltype($1)>::type), "not enough temp space for $1_basetype");
|
||||
%typemap(in) std::function {
|
||||
PyObject *func = $input;
|
||||
$1 = ConvertFunc<$1_ltype>("$symname", func, exHandle$argnum);
|
||||
}
|
||||
|
||||
%typemap(argout) std::function (ExceptionHandling exHandle) {
|
||||
if(exHandle.failFlag) {
|
||||
PyErr_Restore(exHandle.exObj, exHandle.valueObj, exHandle.tracebackObj);
|
||||
SWIG_fail;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// inserted code to include C++ conversion header
|
||||
|
||||
%{
|
||||
#include "renderdoc_replay.h"
|
||||
|
||||
static char convert_error[1024] = {};
|
||||
|
||||
tempalloc($1, tempmem);
|
||||
#include "Code/pyrenderdoc/pyconversion.h"
|
||||
|
||||
int failIdx = 0;
|
||||
int res = TypeConversion<std::remove_pointer<decltype($1)>::type>::ConvertFromPy($input, indirect($1), &failIdx);
|
||||
// declare the basic building blocks as stringize types
|
||||
DECLARE_REFLECTION_STRUCT(int8_t);
|
||||
DECLARE_REFLECTION_STRUCT(uint8_t);
|
||||
DECLARE_REFLECTION_STRUCT(int16_t);
|
||||
DECLARE_REFLECTION_STRUCT(uint16_t);
|
||||
DECLARE_REFLECTION_STRUCT(int32_t);
|
||||
DECLARE_REFLECTION_STRUCT(uint32_t);
|
||||
DECLARE_REFLECTION_STRUCT(int64_t);
|
||||
DECLARE_REFLECTION_STRUCT(uint64_t);
|
||||
DECLARE_REFLECTION_STRUCT(float);
|
||||
DECLARE_REFLECTION_STRUCT(double);
|
||||
DECLARE_REFLECTION_STRUCT(rdcstr);
|
||||
|
||||
if(!SWIG_IsOK(res))
|
||||
{
|
||||
if(res == SWIG_TypeError)
|
||||
{
|
||||
SWIG_exception_fail(SWIG_ArgError(res), "in method '$symname' argument $argnum of type '$1_basetype'");
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' argument $argnum of type '$1_basetype', decoding element %d", failIdx);
|
||||
SWIG_exception_fail(SWIG_ArgError(res), convert_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
%}
|
||||
|
||||
%typemap(freearg, fragment="pyconvert") ContainerType {
|
||||
tempdealloc($1);
|
||||
}
|
||||
|
||||
%typemap(out, fragment="pyconvert") ContainerType {
|
||||
int failIdx = 0;
|
||||
$result = TypeConversion<std::remove_pointer<$1_basetype>::type>::ConvertToPy( indirect($1), &failIdx);
|
||||
if(!$result)
|
||||
{
|
||||
snprintf(convert_error, sizeof(convert_error)-1, "in method '$symname' returning type '$1_basetype', encoding element %d", failIdx);
|
||||
SWIG_exception_fail(SWIG_ValueError, convert_error);
|
||||
}
|
||||
}
|
||||
|
||||
%enddef
|
||||
|
||||
%define CONTAINER_TYPEMAPS(ContainerType)
|
||||
|
||||
CONTAINER_TYPEMAPS_VARIANT(ContainerType)
|
||||
CONTAINER_TYPEMAPS_VARIANT(ContainerType *)
|
||||
CONTAINER_TYPEMAPS_VARIANT(ContainerType &)
|
||||
|
||||
%enddef
|
||||
%include "container_handling.i"
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
<ClCompile Include="pyrenderdoc_stub.cpp" />
|
||||
<CustomBuild Include="renderdoc.i">
|
||||
<FileType>Document</FileType>
|
||||
<AdditionalInputs>%(Fullpath);$(SolutionDir)renderdoc\api\replay\basic_types.h;$(SolutionDir)renderdoc\api\replay\capture_options.h;$(SolutionDir)renderdoc\api\replay\control_types.h;$(SolutionDir)renderdoc\api\replay\d3d11_pipestate.h;$(SolutionDir)renderdoc\api\replay\d3d12_pipestate.h;$(SolutionDir)renderdoc\api\replay\data_types.h;$(SolutionDir)renderdoc\api\replay\gl_pipestate.h;$(SolutionDir)renderdoc\api\replay\renderdoc_replay.h;$(SolutionDir)renderdoc\api\replay\replay_enums.h;$(SolutionDir)renderdoc\api\replay\shader_types.h;$(SolutionDir)renderdoc\api\replay\version.h;$(SolutionDir)renderdoc\api\replay\vk_pipestate.h;%(AdditionalInputs)</AdditionalInputs>
|
||||
<AdditionalInputs>%(Fullpath);container_handling.i;pyconversion.i;cosmetics.i;$(SolutionDir)renderdoc\api\replay\basic_types.h;$(SolutionDir)renderdoc\api\replay\capture_options.h;$(SolutionDir)renderdoc\api\replay\control_types.h;$(SolutionDir)renderdoc\api\replay\d3d11_pipestate.h;$(SolutionDir)renderdoc\api\replay\d3d12_pipestate.h;$(SolutionDir)renderdoc\api\replay\data_types.h;$(SolutionDir)renderdoc\api\replay\gl_pipestate.h;$(SolutionDir)renderdoc\api\replay\renderdoc_replay.h;$(SolutionDir)renderdoc\api\replay\replay_enums.h;$(SolutionDir)renderdoc\api\replay\shader_types.h;$(SolutionDir)renderdoc\api\replay\version.h;$(SolutionDir)renderdoc\api\replay\vk_pipestate.h;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Command>$(ProjectDir)..\..\3rdparty\swig\swig.exe -v -Wextra -Werror -O -c++ -python -modern -modernargs -enumclass -fastunpack -py3 -builtin -I$(SolutionDir)renderdoc\api\replay -outdir $(IntDir)generated -o $(IntDir)generated\%(Filename)_python.cxx %(FullPath)</Command>
|
||||
<Message>Compiling SWIG interface</Message>
|
||||
<Outputs>$(IntDir)generated\%(Filename).py;$(IntDir)generated\r%(Filename)_python.cxx;%(Outputs)</Outputs>
|
||||
|
||||
@@ -10,21 +10,32 @@
|
||||
|
||||
%begin %{
|
||||
|
||||
#undef slots
|
||||
#undef slots
|
||||
%}
|
||||
|
||||
%{
|
||||
#define ENABLE_QT_CONVERT
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QTimeZone>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
|
||||
#include "datetime.h"
|
||||
|
||||
%}
|
||||
|
||||
%include "pyconversion.i"
|
||||
|
||||
// import the renderdoc interface that we depend on
|
||||
%import "renderdoc.i"
|
||||
|
||||
SIMPLE_TYPEMAPS(QString)
|
||||
SIMPLE_TYPEMAPS(QDateTime)
|
||||
SIMPLE_TYPEMAPS(QPair)
|
||||
|
||||
CONTAINER_TYPEMAPS(QList)
|
||||
CONTAINER_TYPEMAPS(QStringList)
|
||||
CONTAINER_TYPEMAPS(QVector)
|
||||
CONTAINER_TYPEMAPS(QMap)
|
||||
TEMPLATE_ARRAY_DECLARE(rdcarray);
|
||||
|
||||
// pass QWidget objects to PySide
|
||||
%typemap(in) QWidget * {
|
||||
@@ -50,17 +61,6 @@ CONTAINER_TYPEMAPS(QMap)
|
||||
%rename("%(regex:/^I([A-Z].*)/\\1/)s", %$isclass) "";
|
||||
|
||||
%{
|
||||
#define ENABLE_QT_CONVERT
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QTimeZone>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
|
||||
#include "datetime.h"
|
||||
|
||||
#ifndef slots
|
||||
#define slots
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Baldur Karlsson
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef ENABLE_QT_CONVERT
|
||||
|
||||
template <>
|
||||
struct TypeConversion<QString, false>
|
||||
{
|
||||
static int ConvertFromPy(PyObject *in, QString &out)
|
||||
{
|
||||
if(PyUnicode_Check(in))
|
||||
{
|
||||
PyObject *bytes = PyUnicode_AsUTF8String(in);
|
||||
|
||||
if(!bytes)
|
||||
return SWIG_ERROR;
|
||||
|
||||
char *buf = NULL;
|
||||
Py_ssize_t size = 0;
|
||||
|
||||
int ret = PyBytes_AsStringAndSize(bytes, &buf, &size);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
out = QString::fromUtf8(buf, (int)size);
|
||||
|
||||
Py_DecRef(bytes);
|
||||
|
||||
return SWIG_OK;
|
||||
}
|
||||
|
||||
Py_DecRef(bytes);
|
||||
|
||||
return SWIG_ERROR;
|
||||
}
|
||||
|
||||
return SWIG_ERROR;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QString &in)
|
||||
{
|
||||
QByteArray bytes = in.toUtf8();
|
||||
return PyUnicode_FromStringAndSize(bytes.data(), bytes.size());
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TypeConversion<QDateTime, false>
|
||||
{
|
||||
static int ConvertFromPy(PyObject *in, QDateTime &out)
|
||||
{
|
||||
if(!PyDateTime_Check(in))
|
||||
return SWIG_TypeError;
|
||||
|
||||
QDate date(PyDateTime_GET_YEAR(in), PyDateTime_GET_MONTH(in), PyDateTime_GET_DAY(in));
|
||||
QTime time(PyDateTime_DATE_GET_HOUR(in), PyDateTime_DATE_GET_MINUTE(in),
|
||||
PyDateTime_DATE_GET_SECOND(in), PyDateTime_DATE_GET_MICROSECOND(in) / 1000);
|
||||
|
||||
out = QDateTime(date, time, QTimeZone::utc());
|
||||
|
||||
return SWIG_OK;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QDateTime &in)
|
||||
{
|
||||
QDate date = in.date();
|
||||
QTime time = in.time();
|
||||
return PyDateTime_FromDateAndTime(date.year(), date.month(), date.day(), time.hour(),
|
||||
time.minute(), time.second(), time.msec() * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Container, typename U>
|
||||
struct ContainerConversion
|
||||
{
|
||||
// we add some extra parameters so the typemaps for array can use these to get
|
||||
// nicer failure error messages out with the index that failed
|
||||
static int ConvertFromPy(PyObject *in, Container &out, int *failIdx)
|
||||
{
|
||||
if(!PyList_Check(in))
|
||||
return SWIG_TypeError;
|
||||
|
||||
Py_ssize_t len = PyList_Size(in);
|
||||
|
||||
for(Py_ssize_t i = 0; i < len; i++)
|
||||
{
|
||||
U u;
|
||||
int ret = TypeConversion<U>::ConvertFromPy(PyList_GetItem(in, i), u);
|
||||
if(!SWIG_IsOK(ret))
|
||||
{
|
||||
if(failIdx)
|
||||
*failIdx = i;
|
||||
return ret;
|
||||
}
|
||||
out.append(u);
|
||||
}
|
||||
|
||||
return SWIG_OK;
|
||||
}
|
||||
|
||||
static int ConvertFromPy(PyObject *in, Container &out) { return ConvertFromPy(in, out, NULL); }
|
||||
static PyObject *ConvertToPyInPlace(PyObject *list, const Container &in, int *failIdx)
|
||||
{
|
||||
for(int i = 0; i < in.size(); i++)
|
||||
{
|
||||
PyObject *elem = TypeConversion<U>::ConvertToPy(in[i]);
|
||||
|
||||
if(elem)
|
||||
{
|
||||
PyList_Append(list, elem);
|
||||
// release our reference
|
||||
Py_DecRef(elem);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(failIdx)
|
||||
*failIdx = i;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const Container &in, int *failIdx)
|
||||
{
|
||||
PyObject *list = PyList_New(0);
|
||||
if(!list)
|
||||
return NULL;
|
||||
|
||||
PyObject *ret = ConvertToPyInPlace(list, in, failIdx);
|
||||
|
||||
// if a failure happened, don't leak the list we created
|
||||
if(!ret)
|
||||
Py_XDECREF(list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const Container &in) { return ConvertToPy(in, NULL); }
|
||||
};
|
||||
|
||||
template <typename U>
|
||||
struct TypeConversion<QList<U>, false> : ContainerConversion<QList<U>, U>
|
||||
{
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TypeConversion<QStringList, false> : ContainerConversion<QList<QString>, QString>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename U>
|
||||
struct TypeConversion<QVector<U>, false> : ContainerConversion<QVector<U>, U>
|
||||
{
|
||||
};
|
||||
|
||||
// specialisation for pair
|
||||
template <typename A, typename B>
|
||||
struct TypeConversion<QPair<A, B>, false>
|
||||
{
|
||||
static int ConvertFromPy(PyObject *in, QPair<A, B> &out)
|
||||
{
|
||||
if(!PyTuple_Check(in))
|
||||
return SWIG_TypeError;
|
||||
|
||||
Py_ssize_t size = PyTuple_Size(in);
|
||||
|
||||
if(size != 2)
|
||||
return SWIG_TypeError;
|
||||
|
||||
int ret = TypeConversion<A>::ConvertFromPy(PyTuple_GetItem(in, 0), out.first);
|
||||
if(SWIG_IsOK(ret))
|
||||
ret = TypeConversion<B>::ConvertFromPy(PyTuple_GetItem(in, 1), out.second);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QPair<A, B> &in)
|
||||
{
|
||||
PyObject *first = TypeConversion<A>::ConvertToPy(in.first);
|
||||
if(!first)
|
||||
return NULL;
|
||||
|
||||
PyObject *second = TypeConversion<B>::ConvertToPy(in.second);
|
||||
if(!second)
|
||||
return NULL;
|
||||
|
||||
PyObject *ret = PyTuple_New(2);
|
||||
if(!ret)
|
||||
return NULL;
|
||||
|
||||
PyTuple_SetItem(ret, 0, first);
|
||||
PyTuple_SetItem(ret, 1, second);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename K, typename V>
|
||||
struct TypeConversion<QMap<K, V>, false>
|
||||
{
|
||||
// we add some extra parameters so the typemaps for array can use these to get
|
||||
// nicer failure error messages out with the index that failed
|
||||
static int ConvertFromPy(PyObject *in, QMap<K, V> &out, int *failIdx)
|
||||
{
|
||||
if(!PyDict_Check(in))
|
||||
return SWIG_TypeError;
|
||||
|
||||
PyObject *keys = PyDict_Keys(in);
|
||||
|
||||
if(!keys)
|
||||
return SWIG_TypeError;
|
||||
|
||||
Py_ssize_t len = PyList_Size(keys);
|
||||
|
||||
for(Py_ssize_t i = 0; i < len; i++)
|
||||
{
|
||||
K k;
|
||||
V v;
|
||||
|
||||
PyObject *key = PyList_GetItem(keys, i);
|
||||
PyObject *value = PyDict_GetItem(in, key);
|
||||
int ret = TypeConversion<K>::ConvertFromPy(key, k);
|
||||
int ret2 = TypeConversion<V>::ConvertFromPy(value, v);
|
||||
if(!SWIG_IsOK(ret) || !SWIG_IsOK(ret2))
|
||||
{
|
||||
if(failIdx)
|
||||
*failIdx = i;
|
||||
Py_DecRef(keys);
|
||||
if(!SWIG_IsOK(ret))
|
||||
return ret;
|
||||
else
|
||||
return ret2;
|
||||
}
|
||||
out.insert(k, v);
|
||||
}
|
||||
|
||||
Py_DecRef(keys);
|
||||
|
||||
return SWIG_OK;
|
||||
}
|
||||
|
||||
static int ConvertFromPy(PyObject *in, QMap<K, V> &out) { return ConvertFromPy(in, out, NULL); }
|
||||
static PyObject *ConvertToPyInPlace(PyObject *pymap, const QMap<K, V> &in, int *failIdx)
|
||||
{
|
||||
QList<K> keys = in.keys();
|
||||
|
||||
for(int i = 0; i < keys.size(); i++)
|
||||
{
|
||||
const K &k = keys[i];
|
||||
PyObject *key = TypeConversion<K>::ConvertToPy(k);
|
||||
|
||||
if(key)
|
||||
{
|
||||
PyObject *value = TypeConversion<V>::ConvertToPy(in[k]);
|
||||
|
||||
if(value)
|
||||
{
|
||||
PyDict_SetItem(pymap, key, value);
|
||||
// release our reference
|
||||
Py_DecRef(key);
|
||||
// release our reference
|
||||
Py_DecRef(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
// destroy unused key
|
||||
Py_DecRef(key);
|
||||
}
|
||||
|
||||
if(failIdx)
|
||||
*failIdx = i;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pymap;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QMap<K, V> &in, int *failIdx)
|
||||
{
|
||||
PyObject *list = PyDict_New();
|
||||
if(!list)
|
||||
return NULL;
|
||||
|
||||
PyObject *ret = ConvertToPyInPlace(list, in, failIdx);
|
||||
|
||||
// if a failure happened, don't leak the map we created
|
||||
if(!ret)
|
||||
Py_XDECREF(list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *ConvertToPy(const QMap<K, V> &in) { return ConvertToPy(in, NULL); }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -42,42 +42,13 @@
|
||||
|
||||
%}
|
||||
|
||||
%fragment("pyconvert", "header") {
|
||||
static char convert_error[1024] = {};
|
||||
|
||||
%#include "Code/pyrenderdoc/pyconversion.h"
|
||||
}
|
||||
|
||||
%include "pyconversion.i"
|
||||
|
||||
SIMPLE_TYPEMAPS(rdcstr)
|
||||
|
||||
CONTAINER_TYPEMAPS(rdcarray)
|
||||
CONTAINER_TYPEMAPS(rdcpair)
|
||||
// Add custom conversion/__str__/__repr__ functions for beautification
|
||||
%include "cosmetics.i"
|
||||
|
||||
FIXED_ARRAY_TYPEMAPS(ResourceId)
|
||||
FIXED_ARRAY_TYPEMAPS(double)
|
||||
FIXED_ARRAY_TYPEMAPS(float)
|
||||
FIXED_ARRAY_TYPEMAPS(bool)
|
||||
FIXED_ARRAY_TYPEMAPS(uint64_t)
|
||||
FIXED_ARRAY_TYPEMAPS(uint32_t)
|
||||
FIXED_ARRAY_TYPEMAPS(int32_t)
|
||||
FIXED_ARRAY_TYPEMAPS(uint16_t)
|
||||
|
||||
%typemap(in, fragment="pyconvert") std::function {
|
||||
PyObject *func = $input;
|
||||
$1 = ConvertFunc<$1_ltype>("$symname", func, exHandle$argnum);
|
||||
}
|
||||
|
||||
%typemap(argout) std::function (ExceptionHandling exHandle) {
|
||||
if(exHandle.failFlag) {
|
||||
PyErr_Restore(exHandle.exObj, exHandle.valueObj, exHandle.tracebackObj);
|
||||
SWIG_fail;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore some operators SWIG doesn't have to worry about
|
||||
// ignore array members
|
||||
// ignore all the array member functions, only wrap the ones that are in python's list
|
||||
%ignore rdcarray::rdcarray;
|
||||
%ignore rdcarray::begin;
|
||||
%ignore rdcarray::end;
|
||||
@@ -94,6 +65,7 @@ FIXED_ARRAY_TYPEMAPS(uint16_t)
|
||||
%ignore rdcarray::empty;
|
||||
%ignore rdcarray::isEmpty;
|
||||
%ignore rdcarray::resize;
|
||||
%ignore rdcarray::clear;
|
||||
%ignore rdcarray::reserve;
|
||||
%ignore rdcarray::swap;
|
||||
%ignore rdcarray::push_back;
|
||||
@@ -102,52 +74,25 @@ FIXED_ARRAY_TYPEMAPS(uint16_t)
|
||||
%ignore rdcstr::operator=;
|
||||
%ignore rdcstr::operator std::string;
|
||||
|
||||
// add __str__ functions
|
||||
%feature("python:tp_str") ResourceId "resid_str";
|
||||
%feature("python:tp_repr") ResourceId "resid_str";
|
||||
%feature("python:nb_int") ResourceId "resid_int";
|
||||
SIMPLE_TYPEMAPS(rdcstr)
|
||||
SIMPLE_TYPEMAPS(bytebuf)
|
||||
|
||||
%wrapper %{
|
||||
static PyObject *resid_str(PyObject *resid)
|
||||
{
|
||||
void *resptr = NULL;
|
||||
unsigned long long *id = NULL;
|
||||
int res = SWIG_ConvertPtr(resid, &resptr, SWIGTYPE_p_ResourceId, 0);
|
||||
if (!SWIG_IsOK(res)) {
|
||||
SWIG_exception_fail(SWIG_ArgError(res), "in method 'ResourceId.str', ResourceId is not correct type");
|
||||
}
|
||||
FIXED_ARRAY_TYPEMAPS(ResourceId)
|
||||
FIXED_ARRAY_TYPEMAPS(double)
|
||||
FIXED_ARRAY_TYPEMAPS(float)
|
||||
FIXED_ARRAY_TYPEMAPS(bool)
|
||||
FIXED_ARRAY_TYPEMAPS(uint64_t)
|
||||
FIXED_ARRAY_TYPEMAPS(uint32_t)
|
||||
FIXED_ARRAY_TYPEMAPS(int32_t)
|
||||
FIXED_ARRAY_TYPEMAPS(uint16_t)
|
||||
|
||||
// cast as unsigned long long
|
||||
id = (unsigned long long *)resptr;
|
||||
static_assert(sizeof(unsigned long long) == sizeof(ResourceId), "Wrong size");
|
||||
// these types are to be treated like python lists/arrays, and will be instantiated after declaration
|
||||
// below
|
||||
TEMPLATE_ARRAY_DECLARE(rdcarray);
|
||||
|
||||
return PyUnicode_FromFormat("<ResourceId %llu>", *id);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject *resid_int(PyObject *resid)
|
||||
{
|
||||
void *resptr = NULL;
|
||||
unsigned long long *id = NULL;
|
||||
int res = SWIG_ConvertPtr(resid, &resptr, SWIGTYPE_p_ResourceId, 0);
|
||||
if (!SWIG_IsOK(res)) {
|
||||
SWIG_exception_fail(SWIG_ArgError(res), "in method 'ResourceId.str', ResourceId is not correct type");
|
||||
}
|
||||
|
||||
// cast as unsigned long long
|
||||
id = (unsigned long long *)resptr;
|
||||
static_assert(sizeof(unsigned long long) == sizeof(ResourceId), "Wrong size");
|
||||
|
||||
return PyLong_FromUnsignedLongLong(*id);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
%}
|
||||
|
||||
%{
|
||||
#include "renderdoc_replay.h"
|
||||
%}
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Actually include header files here. Note that swig is configured not to recurse, so we
|
||||
// need to list all headers in include order that we want to process
|
||||
|
||||
%include <stdint.i>
|
||||
|
||||
@@ -163,6 +108,25 @@ fail:
|
||||
%include "shader_types.h"
|
||||
%include "vk_pipestate.h"
|
||||
|
||||
%feature("docstring") "";
|
||||
|
||||
%extend rdcarray {
|
||||
// we ignored insert and clear before, need to restore them so we can declare our own impls
|
||||
%rename("%s") insert;
|
||||
%rename("%s") clear;
|
||||
}
|
||||
|
||||
// add python array members that aren't in slots
|
||||
EXTEND_ARRAY_CLASS_METHODS(rdcarray)
|
||||
|
||||
// list of array types. These are the concrete types used in rdcarray that will be bound
|
||||
// If you get an error with add_your_use_of_rdcarray_to_swig_interface missing, add your type here
|
||||
// or in qrenderdoc.i, depending on which one is appropriate
|
||||
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, int)
|
||||
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, rdcstr)
|
||||
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, float)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// declare a function for passing external objects into python
|
||||
%wrapper %{
|
||||
|
||||
@@ -177,6 +141,9 @@ PyObject *PassObjectToPython(const char *type, void *obj)
|
||||
|
||||
%}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Check documentation for types is set up correctly
|
||||
|
||||
%header %{
|
||||
#include <set>
|
||||
#include "Code/pyrenderdoc/document_check.h"
|
||||
|
||||
@@ -867,7 +867,10 @@
|
||||
<ClInclude Include="Code\Interface\QRDInterface.h" />
|
||||
<ClInclude Include="Code\Interface\RemoteHost.h" />
|
||||
<ClInclude Include="Code\precompiled.h" />
|
||||
<ClInclude Include="Code\pyrenderdoc\container_handling.h" />
|
||||
<ClInclude Include="Code\pyrenderdoc\document_check.h" />
|
||||
<ClInclude Include="Code\pyrenderdoc\function_conversion.h" />
|
||||
<ClInclude Include="Code\pyrenderdoc\qt_conversion.h" />
|
||||
<ClInclude Include="Code\Resources.h" />
|
||||
<ClInclude Include="$(IntDir)generated\ui_AboutDialog.h" />
|
||||
<ClInclude Include="$(IntDir)generated\ui_APIInspector.h" />
|
||||
@@ -1236,7 +1239,7 @@
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="Code\pyrenderdoc\renderdoc.i">
|
||||
<FileType>Document</FileType>
|
||||
<AdditionalInputs>%(Fullpath);pyconversion.i;document_check.h;$(SolutionDir)renderdoc\api\replay\basic_types.h;$(SolutionDir)renderdoc\api\replay\capture_options.h;$(SolutionDir)renderdoc\api\replay\control_types.h;$(SolutionDir)renderdoc\api\replay\d3d11_pipestate.h;$(SolutionDir)renderdoc\api\replay\d3d12_pipestate.h;$(SolutionDir)renderdoc\api\replay\data_types.h;$(SolutionDir)renderdoc\api\replay\gl_pipestate.h;$(SolutionDir)renderdoc\api\replay\renderdoc_replay.h;$(SolutionDir)renderdoc\api\replay\replay_enums.h;$(SolutionDir)renderdoc\api\replay\shader_types.h;$(SolutionDir)renderdoc\api\replay\vk_pipestate.h;%(AdditionalInputs)</AdditionalInputs>
|
||||
<AdditionalInputs>%(Fullpath);container_handling.i;pyconversion.i;cosmetics.i;document_check.h;$(SolutionDir)renderdoc\api\replay\basic_types.h;$(SolutionDir)renderdoc\api\replay\capture_options.h;$(SolutionDir)renderdoc\api\replay\control_types.h;$(SolutionDir)renderdoc\api\replay\d3d11_pipestate.h;$(SolutionDir)renderdoc\api\replay\d3d12_pipestate.h;$(SolutionDir)renderdoc\api\replay\data_types.h;$(SolutionDir)renderdoc\api\replay\gl_pipestate.h;$(SolutionDir)renderdoc\api\replay\renderdoc_replay.h;$(SolutionDir)renderdoc\api\replay\replay_enums.h;$(SolutionDir)renderdoc\api\replay\shader_types.h;$(SolutionDir)renderdoc\api\replay\vk_pipestate.h;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Command>"$(ProjectDir)3rdparty\swig\swig.exe" -v -Wextra -Werror -O -c++ -python -modern -modernargs -enumclass -fastunpack -py3 -builtin -I"$(SolutionDir)renderdoc\api\replay" -outdir "$(IntDir)generated" -o "$(IntDir)generated\%(Filename)_python.cxx" "%(FullPath)"</Command>
|
||||
<Message>Compiling SWIG interface</Message>
|
||||
<Outputs>$(IntDir)generated\%(Filename).py;$(IntDir)generated\%(Filename)_python.cxx;%(Outputs)</Outputs>
|
||||
@@ -1447,8 +1450,10 @@
|
||||
</CustomBuild>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Code\pyrenderdoc\container_handling.i" />
|
||||
<None Include="Code\pyrenderdoc\pyconversion.h" />
|
||||
<None Include="Code\pyrenderdoc\pyconversion.i" />
|
||||
<None Include="Code\pyrenderdoc\cosmetics.i" />
|
||||
<None Include="Resources\128.png" />
|
||||
<None Include="Resources\accept.png" />
|
||||
<None Include="Resources\add.png" />
|
||||
|
||||
@@ -986,6 +986,15 @@
|
||||
<ClInclude Include="$(IntDir)generated\ui_PerformanceCounterViewer.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Code\pyrenderdoc\container_handling.h">
|
||||
<Filter>Code\pyrenderdoc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Code\pyrenderdoc\function_conversion.h">
|
||||
<Filter>Code\pyrenderdoc</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Code\pyrenderdoc\qt_conversion.h">
|
||||
<Filter>Code\pyrenderdoc</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources\128.png">
|
||||
@@ -1168,6 +1177,12 @@
|
||||
<None Include="Code\pyrenderdoc\pyconversion.i">
|
||||
<Filter>Code\pyrenderdoc</Filter>
|
||||
</None>
|
||||
<None Include="Code\pyrenderdoc\cosmetics.i">
|
||||
<Filter>Code\pyrenderdoc</Filter>
|
||||
</None>
|
||||
<None Include="Code\pyrenderdoc\container_handling.i">
|
||||
<Filter>Code\pyrenderdoc</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Resources\qrenderdoc.rc">
|
||||
|
||||
Reference in New Issue
Block a user