Workspaces: Python to C
I've been thinking about how we'd convert the workspaces from Python objects to C in a semi-neat manner, given we rely on inheritance of some variables for convenience. We could use C++, but I don't really want to throw another language in the project.
So, here's some basic inheritance pattern using structs and unions. Mostly writing this up so I don't forget it. The Python Workspace objects then have a struct which contains all the required values and function pointers needed to run calculations in no GIL mode:
%%cython
from libc.stdlib cimport free, calloc
# Define some common function pointers
ctypedef int (*fptr)(void*)
# Some basic struct that contains all the info we need to
# run in nogil mode
ctypedef struct A:
int a
double d
double e
fptr fill
# Some basic inheritance C like behaviour
# Union to store the base class
cdef union B_parent:
A A
ctypedef struct B:
B_parent base # BASE CLASS UNION MUST ALWAYS BE FIRST!
int b
fptr do_something
# We can make shortcuts straight to lower
# base classes by including them in the union
# e.g. these are valid.
# C.base.A.fill()
# C.base.B.do_something()
# Otherwise you'd have to do:
# C.base.B.base.A.fill()
# C11 has annoymous unions which mean you wouldn't have to write .base all the time
# however cython doesn't support it. Probably not backwards compatiable with MSVC.
cdef union C_parents:
A A
B B
ctypedef struct C:
C_parents base
int c
cdef int fa(void* ptr):
cdef A *a = <A*>ptr
return a.a
cdef int fb(void* ptr):
cdef B *b = <B*>ptr
return b.b
cdef void init_A(A* ptr, a):
ptr.a = a
ptr.d = 0
ptr.e = 0
ptr.fill = fa
cdef void init_B(B* ptr, b):
ptr.b = b
ptr.do_something = fb
cdef void init_C(C* ptr, int c):
ptr.c = c
# Now we can wrap up these structs into a Python class for easier access.
cdef class GenericWorkspace:
# Specify the size of the data here
__struct_size__ = 0 # Generic stores no data struct
cdef void* _ws # The workspace ptr, what type it is depends on what has inherited this class
def __cinit__(self):
if hasattr(self, '__struct_size__'):
if(self.__struct_size__ <= 0):
raise Exception("Size of data struct must be __struct_size__ > 0")
# Ensures this is NULL for inheriting classes to
# check if memory is already available
self._ws = calloc(1, self.__struct_size__)
if not self._ws:
raise MemoryError()
else:
# Pure python workspaces won't need a _ws
self._ws = NULL
def __dealloc__(self):
if self._ws:
free(self._ws)
# Show how to do inheritance
cdef class WorkspaceA(GenericWorkspace):
# Specify the size of the data here so GenericWorkspace knows what to do with it
__struct_size__ = sizeof(A)
def __init__(WorkspaceA self, int a):
init_A(<A*>self._ws, a)
cpdef fill(self):
return (<A*>self._ws).fill(self._ws)
cdef class WorkspaceB(WorkspaceA):
__struct_size__ = sizeof(B)
def __init__(WorkspaceB self, int a, int b):
super().__init__(a)
init_B(<B*>self._ws, b)
cpdef do_something(self):
return (<B*>self._ws).do_something(self._ws)
cdef class WorkspaceC(WorkspaceB):
__struct_size__ = sizeof(C)
def __init__(WorkspaceB self, int a, int b, int c):
super().__init__(a, b)
init_C(<C*>self._ws, c)
Example to show it's working.
wsc = WorkspaceC(100, 20, 3)
print(wsc.fill())
print(wsc.do_something())
wsb = WorkspaceB(100, 20)
print(wsb.fill())
print(wsb.do_something())
wsa = WorkspaceA(100)
print(wsa.fill())