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:
baldurk
2017-11-03 16:19:41 +00:00
parent 81aa36e24b
commit 9db71b803a
12 changed files with 1263 additions and 618 deletions
@@ -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
+43
View File
@@ -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);
};
}
+8 -479
View File
@@ -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"
+46 -45
View File
@@ -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>
+17 -17
View File
@@ -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
+323
View File
@@ -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 -75
View File
@@ -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"
+6 -1
View File
@@ -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">