Imported core, m, ag and serial for refactoring

This commit is contained in:
Overwatch 2014-08-05 10:19:01 +02:00
parent 6f28ac0382
commit 5baa9b75d0
25 changed files with 918 additions and 4 deletions

0
ag/__init__.py Normal file
View file

160
ag/ag.Aggregate.pyx Normal file
View file

@ -0,0 +1,160 @@
from collections import OrderedDict
cdef class Aggregate:
def __init__(Aggregate self, *modules):
self._attrs = {} # attr_name, module
self._modules = OrderedDict() # module_name, module
self._provided = {} # keyword, module
self._common = {} # method_name, method_wrapper
for module in modules:
self._link_module(module)
def _list_modules(Aggregate self):
d = {}
for attr, module in self._attrs.items():
if module not in d:
d[module] = set()
d[module].add(attr)
return d
def _link_module(Aggregate self, Module module):
# check for name (=type) collision
if module._name in self._modules.keys():
raise ModuleCollision(module._name, None, "name", None)
# check if requirements are satisfied
unsatisfied_deps = module._requires - set(self._provided.keys())
if unsatisfied_deps:
raise ModuleDependencyError(module._name, unsatisfied_deps)
# check for new module declaring a common method that we already provide as non-common
new_commons = module._common - set(self._common.keys())
common_collisions = {nc for nc in new_commons if nc in self._attrs and not nc in self._common.keys()}
if common_collisions:
colliding_module_names = {self._attrs[x]._name for x in common_collisions}
raise ModuleCollision(module._name, colliding_module_names, "non-common method", common_collisions)
# check for an attr collision
module_attrs = {x for x in dir(module) if x[0] != "_"}
attr_collisions = (module_attrs - module._common) & (set(self._attrs.keys()) | set(self._common.keys()))
if attr_collisions:
colliding_module_names = set()
for collision in attr_collisions:
if collision in self._attrs:
colliding_module_names.add(self._attrs[collision]._name)
if collision in self._common:
colliding_module_names.add(self._common[collision]._name)
raise ModuleCollision(module._name, colliding_module_names, "attribute", attr_collisions)
# check for a provided keyword collision
provided_collisions = module._provides & set(self._provided.keys())
if provided_collisions:
colliding_module_names = {self._provided[x]._name for x in provided_collisions}
raise ModuleCollision(module._name, colliding_module_names, "provided keyword", provided_collisions)
# link the module
self._modules[module._name] = module
for keyword in module._provides:
self._provided[keyword] = module
for attr in (module_attrs - module._common):
self._attrs[attr] = module
# create and/or populate CommonMethod wrappers to common methods
for method_name in module._common:
if method_name not in module_attrs:
raise CommonMethodMissing(method_name, module._name)
if method_name not in self._common:
self._common[method_name] = CommonMethod(method_name)
self._common[method_name].link_module(module)
# hand the module a reference to us
module._top = self
# call the module's _on_link method, if it has one
if hasattr(module, "_on_link"):
module._on_link()
def _unlink_module(Aggregate self, str module_name):
if not module_name in self._modules:
raise ModuleDoesntExist(module_name)
module = self._modules[module_name]
# check reverse dependencies
global_deps = set()
for m in self._modules.values():
global_deps.update(m._requires)
reverse_deps = module._provides & global_deps
if reverse_deps:
raise ModuleDependencyError(module_name, reverse_deps, unlink=True)
# remove from all pools
for aname, mod in list(self._attrs.items()):
if mod._name == module_name:
del self._attrs[aname]
del self._modules[module_name]
for ename in module._provides:
del self._provided[ename]
# remove _common wrappers
for method_name in module._common:
self._common[method_name].unlink_module(module_name)
# clear _top reference
module._top = None
def _merge_in(Aggregate self, Aggregate other_ag):
for module_name, module in other_ag._modules.items():
if module_name not in self._modules:
self._link_module(module)
def __getattr__(Aggregate self, str aname):
if aname in self._attrs:
return getattr(self._attrs[aname], aname)
elif aname in self._common:
return self._common[aname]
else:
raise AttributeError("Aggregate has no attribute '%s'" %(aname))
def __setattr__(Aggregate self, str aname, avalue):
if aname not in self._attrs:
raise AttributeError("Aggregate has no attribute '%s'" %(aname))
else:
setattr(self._attrs[aname], aname, avalue)
def _get_type(Aggregate self):
return tuple(self._modules.keys())
def __repr__(Aggregate self):
module_count = len(self._modules)
if module_count:
lines = ["Aggregate("]
for i, module in enumerate(self._modules.values()):
if i + 1 < module_count:
comma = ","
else:
comma = ""
lines.append(" %s%s" %(repr(module), comma))
lines.append(")")
return "\n".join(lines)
else:
return "Aggregate()"

30
ag/ag.CommonMethod.pyx Normal file
View file

@ -0,0 +1,30 @@
cdef class CommonMethod:
def __init__(CommonMethod self, str method_name):
self.method_name = method_name
self.module_names = []
self.modules = []
def link_module(CommonMethod self, Module module):
self.module_names.append(module._name)
self.modules.append(module)
def unlink_module(CommonMethod self, module_name):
i = self.module_names.index(module_name)
del self.module_names[i]
del self.modules[i]
def __call__(CommonMethod self, *args):
return_values = []
for module in self.modules:
return_values.append(getattr(module, self.method_name).__call__(*args))
return return_values
def __repr__(self):
return "CommonMethod(name=%s)" %(self.method_name)
@property
def _name(self):
return repr(self)

20
ag/ag.Module.pyx Normal file
View file

@ -0,0 +1,20 @@
cdef class Module:
def __init__(self, name, provides=set(), requires=set(), common=set()):
"""
* Modules are identified by their name which has to be unique within the Aggregate's namespace.
* The provides sequence contains keywords which are copied into Aggregate's _provided list.
Two modules providing the same export can't be linked into the same Aggregate.
* The requires sequence contains keywords that have to be already present in the Aggregate's
_provided list before linking.
* All names in the Module's namespace that don't begin with an underscore will be exported
into the Aggregate's namespace.
* If you want to call one method on multiple modules, these modules must all export the method name
in their "common" set. Otherwise a name collision is raised. Their methods will be called in the
same order in which the modules were linked.
"""
self._top = None
self._name = name
self._provides = set(provides)
self._requires = set(requires)
self._common = set(common)

49
ag/ag.header.pyx Normal file
View file

@ -0,0 +1,49 @@
"""
Overwatch Aggregate Object type
TODO Method overrides: _common gets renamed to _call_all and we add _call_last.
"""
class ModuleCollision(Exception):
def __init__(self, colliding_module_name, resident_module_names, item_type, items):
self.colliding_module_name = colliding_module_name
self.resident_module_names = resident_module_names
self.item_type = item_type
self.items = items
def __str__(self):
if self.item_type == "name":
return "Unable to link module '%s': name already present in Aggregate." %(self.colliding_module_name)
else:
return "Unable to link module '%s': %s(s) '%s' already provided by module(s) '%s'." %(self.colliding_module_name,
self.item_type, "', '".join(self.items), "', '".join(self.resident_module_names))
class ModuleDependencyError(Exception):
def __init__(self, module_name, dependencies, unlink=False):
self.module_name = module_name
self.dependencies = dependencies
self.unlink = unlink
def __str__(self):
if self.unlink:
return "Unable to unlink module '%s': Aggregate depends on its export(s) '%s'." %(self.module_name,
"', '".join(self.dependencies))
else:
return "Unable to link module '%s': requirement '%s' unsatisfied by Aggregate." %(self.module_name,
"', '".join(self.dependencies))
class CommonMethodMissing(Exception):
def __init__(self, method_name, module_name):
self.method_name = method_name
self.module_name = module_name
def __str__(self):
return "Unable to link module '%s': method '%s' (declared as common) does not exist in module." %(
self.module_name, self.method_name)
class ModuleDoesntExist(Exception):
def __init__(self, module_name):
self.module_name = module_name
def __str__(self):
return "Unable to unlink module '%s': not linked to Aggregate." %(self.module_name)

0
ag/build.sh Executable file
View file

View file

@ -1,11 +1,10 @@
#! /bin/zsh #! /bin/zsh
# encoding: utf-8 # encoding: utf-8
for dir in src-[0-9][0-9]_* for dir in ag core m serial
do do
echo "Building in ${dir}" echo "Building in ${dir}"
cd "$dir" cd "$dir"
./build.sh ./build.sh
cd .. cd ..
ln -s $dir/*.so .
done done

0
core/__init__.py Normal file
View file

0
m/__init__.py Normal file
View file

23
m/m.header.pyx Normal file
View file

@ -0,0 +1,23 @@
"""
Vector And Matrix Math
TODO some nice description
"""
from libc.stdlib cimport malloc, realloc, free
from libc.stdint cimport uint8_t, uint16_t, uint64_t
from libc.math cimport sin, cos, sqrt
class MathError(Exception):
def __init__(self, description):
self.description = description
def __str__(self):
return self.description
class GeneralError(Exception):
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

332
m/m.mat4.pyx Normal file
View file

@ -0,0 +1,332 @@
cdef class mat4:
"""
A float 4x4 matrix.
All arrays are column-major, i.e. OpenGL style:
0 4 8 12
1 5 9 13
2 6 10 14
3 7 11 15
The matrix implements stacking useful for graphics.
"""
def __cinit__(mat4 self):
to_alloc = 16 * sizeof(float)
self.stack = <float *>malloc(to_alloc)
if not self.stack:
raise MemoryError("Unable to malloc %d B for mat4." %(to_alloc))
self.m = self.stack
self.size = 1
def _debug(mat4 self):
print("--- self.stack = %d" %(<uint64_t>self.stack))
print("--- self.m = %d (+%d)" %(<uint64_t>self.m, self.m - self.stack))
print("--- self.size = %d" %(self.size))
def __init__(mat4 self, *args):
"""
Create a ma4t.
Accepts any number of parameters between 0 and 16 to fill the
matrix from the upper left corner going down (column-wise).
"""
length = len(args)
if length == 1 and isinstance(args[0], (list, tuple)):
args = args[0]
length = len(args)
if length > 16:
raise MathError("Attempt to initialize a mat4 with %d arguments." %(length))
self.load_from(args)
def __dealloc__(mat4 self):
free(self.stack)
def __getstate__(mat4 self):
state = []
for i in range(self.m - self.stack + 16):
state.append(self.stack[i])
return state
def __setstate__(mat4 self, state):
length = len(state)
matrices = length//16
if not matrices*16 == length:
raise GeneralError("mat4 __setstate__ got %d floats as a state" %(length))
self.m = self.stack
slot_full = False
for start in range(0, length, 16):
if slot_full:
self.push()
slot_full = False
self.load_from(state[start:start+16])
slot_full = True
def __getitem__(mat4 self, int i):
if i > 16 or i < 0:
raise IndexError("element index out of range(16)")
return self.m[i]
def __setitem__(self, int i, value):
if i > 16 or i < 0:
raise IndexError("element index out of range(16)")
self.m[i] = value
def push(mat4 self):
"""
Push the current matrix into the stack and load up an empty one (a zero matrix)
"""
# self.m points to the current matrix
# self.stack points to the first matrix
# self.size how many matrices are allocated
# ensure there's room for one more
cdef unsigned int used = 1 + (self.m - self.stack) / 16
cdef unsigned int empty = self.size - used
cdef float *tmp
if not empty:
self.size += 1
to_alloc = self.size * 16 * sizeof(float)
tmp = <float *>realloc(self.stack, to_alloc)
if tmp:
self.stack = tmp
else:
raise MemoryError("Unable to malloc %d B for mat4." %(to_alloc))
# advance the pointer to the new one
self.m = self.stack + 16 * used
# at this point there's at least enough space for one matrix
# copy the old matrix into the new one
cdef uint8_t i
cdef float *old_m = self.m - 16
for i in range(16):
self.m[i] = old_m[i]
def pop(mat4 self):
"""
Pop a matrix from the stack.
"""
if self.m == self.stack:
raise IndexError("pop from an empty stack")
self.m -= 16
def get_list(mat4 self):
L = []
for i in range(16):
L.append(self.m[i])
return L
def load_from(mat4 self, L):
"""
Fill the current matrix from a either a list of values, column-major,
or another matrix. This method doesn't modify the stack, only the
current matrix is read and modified.
If the number of values isn't 16, it will be padded to 16 by zeros.
If it's larger, GeneralError will be raised.
"""
if isinstance(L, mat4):
L = L.get_list()
length = 16
else:
length = len(L)
if length > 16:
raise GeneralError("supplied list is longer than 16")
for i in range(16):
if i < length:
self.m[i] = L[i]
else:
self.m[i] = 0.0
def zero(mat4 self):
"""Fill the matrix with zeroes."""
for i in range(16):
self.m[i] = 0.0
def identity(mat4 self):
"""Make the matrix an identity."""
self.zero()
self.m[0] = 1.0
self.m[5] = 1.0
self.m[10] = 1.0
self.m[15] = 1.0
def transpose(mat4 self):
"""Transpose the matrix."""
cdef float tmp
tmp = self.m[1]
self.m[1] = self.m[4]
self.m[4] = tmp
tmp = self.m[2]
self.m[2] = self.m[8]
self.m[8] = tmp
tmp = self.m[3]
self.m[3] = self.m[12]
self.m[12] = tmp
tmp = self.m[7]
self.m[7] = self.m[13]
self.m[13] = tmp
tmp = self.m[11]
self.m[11] = self.m[14]
self.m[14] = tmp
tmp = self.m[6]
self.m[6] = self.m[9]
self.m[9] = tmp
def invert(mat4 self):
"""Invert the matrix."""
cdef float tmp[16]
cdef float det
tmp[0] = self.m[5]*self.m[10]*self.m[15] - self.m[5]*self.m[11]*self.m[14] - self.m[9]*self.m[6]*self.m[15] + self.m[9]*self.m[7]*self.m[14] + self.m[13]*self.m[6]*self.m[11] - self.m[13]*self.m[7]*self.m[10]
tmp[4] = -self.m[4]*self.m[10]*self.m[15] + self.m[4]*self.m[11]*self.m[14] + self.m[8]*self.m[6]*self.m[15] - self.m[8]*self.m[7]*self.m[14] - self.m[12]*self.m[6]*self.m[11] + self.m[12]*self.m[7]*self.m[10]
tmp[8] = self.m[4]*self.m[9]*self.m[15] - self.m[4]*self.m[11]*self.m[13] - self.m[8]*self.m[5]*self.m[15] + self.m[8]*self.m[7]*self.m[13] + self.m[12]*self.m[5]*self.m[11] - self.m[12]*self.m[7]*self.m[9]
tmp[12] = -self.m[4]*self.m[9]*self.m[14] + self.m[4]*self.m[10]*self.m[13] + self.m[8]*self.m[5]*self.m[14] - self.m[8]*self.m[6]*self.m[13] - self.m[12]*self.m[5]*self.m[10] + self.m[12]*self.m[6]*self.m[9]
det = self.m[0]*tmp[0] + self.m[1]*tmp[4] + self.m[2]*tmp[8] + self.m[3]*tmp[12]
# epsilon pulled straight out of Uranus
if det < 0.00005 and det > -0.00005:
print("det=%.1f" %(det))
return
tmp[1] = -self.m[1]*self.m[10]*self.m[15] + self.m[1]*self.m[11]*self.m[14] + self.m[9]*self.m[2]*self.m[15] - self.m[9]*self.m[3]*self.m[14] - self.m[13]*self.m[2]*self.m[11] + self.m[13]*self.m[3]*self.m[10]
tmp[5] = self.m[0]*self.m[10]*self.m[15] - self.m[0]*self.m[11]*self.m[14] - self.m[8]*self.m[2]*self.m[15] + self.m[8]*self.m[3]*self.m[14] + self.m[12]*self.m[2]*self.m[11] - self.m[12]*self.m[3]*self.m[10]
tmp[9] = -self.m[0]*self.m[9]*self.m[15] + self.m[0]*self.m[11]*self.m[13] + self.m[8]*self.m[1]*self.m[15] - self.m[8]*self.m[3]*self.m[13] - self.m[12]*self.m[1]*self.m[11] + self.m[12]*self.m[3]*self.m[9]
tmp[13] = self.m[0]*self.m[9]*self.m[14] - self.m[0]*self.m[10]*self.m[13] - self.m[8]*self.m[1]*self.m[14] + self.m[8]*self.m[2]*self.m[13] + self.m[12]*self.m[1]*self.m[10] - self.m[12]*self.m[2]*self.m[9]
tmp[2] = self.m[1]*self.m[6]*self.m[15] - self.m[1]*self.m[7]*self.m[14] - self.m[5]*self.m[2]*self.m[15] + self.m[5]*self.m[3]*self.m[14] + self.m[13]*self.m[2]*self.m[7] - self.m[13]*self.m[3]*self.m[6]
tmp[6] = -self.m[0]*self.m[6]*self.m[15] + self.m[0]*self.m[7]*self.m[14] + self.m[4]*self.m[2]*self.m[15] - self.m[4]*self.m[3]*self.m[14] - self.m[12]*self.m[2]*self.m[7] + self.m[12]*self.m[3]*self.m[6]
tmp[10] = self.m[0]*self.m[5]*self.m[15] - self.m[0]*self.m[7]*self.m[13] - self.m[4]*self.m[1]*self.m[15] + self.m[4]*self.m[3]*self.m[13] + self.m[12]*self.m[1]*self.m[7] - self.m[12]*self.m[3]*self.m[5]
tmp[14] = -self.m[0]*self.m[5]*self.m[14] + self.m[0]*self.m[6]*self.m[13] + self.m[4]*self.m[1]*self.m[14] - self.m[4]*self.m[2]*self.m[13] - self.m[12]*self.m[1]*self.m[6] + self.m[12]*self.m[2]*self.m[5]
tmp[3] = -self.m[1]*self.m[6]*self.m[11] + self.m[1]*self.m[7]*self.m[10] + self.m[5]*self.m[2]*self.m[11] - self.m[5]*self.m[3]*self.m[10] - self.m[9]*self.m[2]*self.m[7] + self.m[9]*self.m[3]*self.m[6]
tmp[7] = self.m[0]*self.m[6]*self.m[11] - self.m[0]*self.m[7]*self.m[10] - self.m[4]*self.m[2]*self.m[11] + self.m[4]*self.m[3]*self.m[10] + self.m[8]*self.m[2]*self.m[7] - self.m[8]*self.m[3]*self.m[6]
tmp[11] = -self.m[0]*self.m[5]*self.m[11] + self.m[0]*self.m[7]*self.m[9] + self.m[4]*self.m[1]*self.m[11] - self.m[4]*self.m[3]*self.m[9] - self.m[8]*self.m[1]*self.m[7] + self.m[8]*self.m[3]*self.m[5]
tmp[15] = self.m[0]*self.m[5]*self.m[10] - self.m[0]*self.m[6]*self.m[9] - self.m[4]*self.m[1]*self.m[10] + self.m[4]*self.m[2]*self.m[9] + self.m[8]*self.m[1]*self.m[6] - self.m[8]*self.m[2]*self.m[5]
det = 1.0 / det
self.m[0] = tmp[0] * det
self.m[1] = tmp[1] * det
self.m[2] = tmp[2] * det
self.m[3] = tmp[3] * det
self.m[4] = tmp[4] * det
self.m[5] = tmp[5] * det
self.m[6] = tmp[6] * det
self.m[7] = tmp[7] * det
self.m[8] = tmp[8] * det
self.m[9] = tmp[9] * det
self.m[10] = tmp[10] * det
self.m[11] = tmp[11] * det
self.m[12] = tmp[12] * det
self.m[13] = tmp[13] * det
self.m[14] = tmp[14] * det
self.m[15] = tmp[15] * det
def mulm(mat4 self, mat4 B, bint inplace=False):
"""
Return a matrix that is the result of multiplying this matrix by another.
M = self * mat4 B
"""
cdef uint8_t i
cdef mat4 tmp = mat4()
tmp.m[0] = self.m[0] * B.m[0] + self.m[4] * B.m[1] + self.m[8] * B.m[2] + self.m[12] * B.m[3]
tmp.m[1] = self.m[1] * B.m[0] + self.m[5] * B.m[1] + self.m[9] * B.m[2] + self.m[13] * B.m[3]
tmp.m[2] = self.m[2] * B.m[0] + self.m[6] * B.m[1] + self.m[10] * B.m[2] + self.m[14] * B.m[3]
tmp.m[3] = self.m[3] * B.m[0] + self.m[7] * B.m[1] + self.m[11] * B.m[2] + self.m[15] * B.m[3]
tmp.m[4] = self.m[0] * B.m[4] + self.m[4] * B.m[5] + self.m[8] * B.m[6] + self.m[12] * B.m[7]
tmp.m[5] = self.m[1] * B.m[4] + self.m[5] * B.m[5] + self.m[9] * B.m[6] + self.m[13] * B.m[7]
tmp.m[6] = self.m[2] * B.m[4] + self.m[6] * B.m[5] + self.m[10] * B.m[6] + self.m[14] * B.m[7]
tmp.m[7] = self.m[3] * B.m[4] + self.m[7] * B.m[5] + self.m[11] * B.m[6] + self.m[15] * B.m[7]
tmp.m[8] = self.m[0] * B.m[8] + self.m[4] * B.m[9] + self.m[8] * B.m[10] + self.m[12] * B.m[11]
tmp.m[9] = self.m[1] * B.m[8] + self.m[5] * B.m[9] + self.m[9] * B.m[10] + self.m[13] * B.m[11]
tmp.m[10] = self.m[2] * B.m[8] + self.m[6] * B.m[9] + self.m[10] * B.m[10] + self.m[14] * B.m[11]
tmp.m[11] = self.m[3] * B.m[8] + self.m[7] * B.m[9] + self.m[11] * B.m[10] + self.m[15] * B.m[11]
tmp.m[12] = self.m[0] * B.m[12] + self.m[4] * B.m[13] + self.m[8] * B.m[14] + self.m[12] * B.m[15]
tmp.m[13] = self.m[1] * B.m[12] + self.m[5] * B.m[13] + self.m[9] * B.m[14] + self.m[13] * B.m[15]
tmp.m[14] = self.m[2] * B.m[12] + self.m[6] * B.m[13] + self.m[10] * B.m[14] + self.m[14] * B.m[15]
tmp.m[15] = self.m[3] * B.m[12] + self.m[7] * B.m[13] + self.m[11] * B.m[14] + self.m[15] * B.m[15]
if inplace:
for i in range(16):
self.m[i] = tmp.m[i]
else:
return tmp
def mulv(mat4 self, vec3 v):
"""
Return a vec3 that is the result of multiplying this matrix by a vec3.
u = self * v
"""
cdef mat4 tmp = vec3()
tmp.v[0] = v.v[0]*self.m[0] + v.v[1]*self.m[4] + v.v[2]*self.m[8] + self.m[12]
tmp.v[1] = v.v[0]*self.m[1] + v.v[1]*self.m[5] + v.v[2]*self.m[9] + self.m[13]
tmp.v[2] = v.v[0]*self.m[2] + v.v[1]*self.m[6] + v.v[2]*self.m[10] + self.m[14]
return tmp
def mulf(mat4 self, f):
"""
Return a matrix that is the result of multiplying this matrix by a scalar.
M = self * f
"""
cdef mat4 tmp = mat4()
cdef int i
for i in range(16):
tmp.m[i] = self.m[i] * f
return tmp
def __repr__(mat4 self):
lines = []
lines.append("mat4(%.1f %.1f %.1f %.1f" %(self.m[0], self.m[4], self.m[8], self.m[12]))
lines.append(" %.1f %.1f %.1f %.1f" %(self.m[1], self.m[5], self.m[9], self.m[13]))
lines.append(" %.1f %.1f %.1f %.1f" %(self.m[2], self.m[6], self.m[10], self.m[14]))
lines.append(" %.1f %.1f %.1f %.1f)" %(self.m[3], self.m[7], self.m[11], self.m[15]))
return "\n".join(lines)

113
m/m.vec3.pyx Normal file
View file

@ -0,0 +1,113 @@
cdef class vec3:
"""
A float 3D vector.
>>> v = vec3(1, 1, 0)
>>> w = vec3(0, 1, 1)
>>> v.length
1.4142135623730951
>>> v.dot(w)
1.0
>>> v.cross(w)
vec4(1.00, 1.00, 1.00)
>>> v + w
vec4(1.00, 2.00, 1.00)
>>> w - v
vec4(-1.00, 0.00, 1.00)
"""
def __init__(vec3 self, *args):
"""
Create a vec3.
Accepts any number of parameters between 0 and 3 to fill the vector from the left.
"""
length = len(args)
if length == 1 and isinstance(args[0], (list, tuple)):
args = args[0]
length = len(args)
if length > 3:
raise MathError("Attempt to initialize a vec3 with %d arguments." %(length))
for i in range(3):
if i < length:
self.v[i] = args[i]
else:
self.v[i] = 0.0
def __getitem__(vec3 self, int i):
if i >= 3 or i < 0:
raise IndexError("element index out of range(3)")
return self.v[i]
def __setitem__(vec3 self, int i, float value):
if i >= 3 or i < 0:
raise IndexError("element index out of range(3)")
self.v[i] = value
def __repr__(vec3 self):
return "vec3(%.2f, %.2f, %.2f)" %(self.v[0], self.v[1], self.v[2])
def __getstate__(vec3 self):
return (self.v[0], self.v[1], self.v[2])
def __setstate__(vec3 self, state):
self.v[0] = state[0]
self.v[1] = state[1]
self.v[2] = state[2]
@property
def length(vec3 self):
"""Contains the geometric length of the vector."""
return sqrt(self.v[0]**2 + self.v[1]**2 + self.v[2]**2)
def normalized(vec3 self):
"""Returns this vector, normalized."""
length = self.length
return vec3(self.v[0] / length, self.v[1] / length, self.v[2] / length)
def __add__(vec3 L, vec3 R):
return vec3(L.v[0] + R.v[0], L.v[1] + R.v[1], L.v[2] + R.v[2])
def __sub__(vec3 L, vec3 R):
return vec3(L.v[0] - R.v[0], L.v[1] - R.v[1], L.v[2] - R.v[2])
def __neg__(vec3 self):
return vec3(-self.v[0], -self.v[1], -self.v[2])
def dot(vec3 L, vec3 R):
"""
Returns the dot product of the two vectors.
E.g. u.dot(v) -> u . v
"""
return L.v[0] * R.v[0] + L.v[1] * R.v[1] + L.v[2] * R.v[2]
def cross(vec3 L, vec3 R):
"""
Returns the cross product of the two vectors.
E.g. u.cross(v) -> u x v
"""
return vec3(L.v[1]*R.v[2] - L.v[2]*R.v[1], L.v[0]*R.v[2] - L.v[2]*R.v[0], L.v[0]*R.v[1] - L.v[1]*R.v[0])
def __mul__(vec3 L, R):
"""
Multiplication of a vec3 by a float.
The float has to be on the right.
"""
return vec3(L.v[0] * R, L.v[1] * R, L.v[2] * R)

0
serial/__init__.py Normal file
View file

0
serial/build.sh Executable file
View file

190
serial/com.py Normal file
View file

@ -0,0 +1,190 @@
#! /bin/env python3
# encoding: utf-8
import time
import serial
from . import et
prefix = et.prefix
"""
===== OverTalk protocol spec =====
==== Transport layer ====
frame = [ 0x2a | destination (1) | source (1) | payload length (1) | payload (n) | checksum (1) ]
Any time an 0x2a is encountered the parser should flush its state and begin reading a new frame.
Bytes 0x2a, 0x2b, 0x11 and 0x13 are prefixed with 0x2b and inverted (XORed with 0xff) to avoid collisions.
Frames with destinations unknown to an OverShell are sent to the default interface, or dropped if
that's the interface they came from.
==== Command layer ====
Payload consists of one or more commands:
get_command = [ 0x00 | register (1) | target register (1) ]
set_command = [ 0x01 | register (1) | length (1) | value (n) ]
With a get_command the sender requests the receiver to read its own "register" and issue
a set_command that sets the sender's "target register" to that value.
A set_command does what is says on the box.
"""
class Transport:
"""
OverTalk Transport layer implementation.
Reads data from multiple interfaces and either router frames or receives them.
A received frame is then made available via a public attribute.
Interfaces are objects that implement methods read() (returns one byte of data as int), write(buffer, len),
and waiting property (contains number of bytes waiting).
"""
def __init__(self, my_id, interfaces, def_route):
"""
@param my_id OverTalk address of this Transport instance
@param interfaces a dict of interfaces keyed by their IDs
@param def_route ID of default interface for sending
"""
assert my_id not in interfaces
assert def_route in interfaces
self._print = et.Output("over.com.Transport", default_suffix="\n", timestamp=True)
self.my_id = my_id
self.def_route = def_route
self.interfaces = interfaces
self.destination_unknown = 0
self.incoming = None
self._print("<Cg>on-line<C/>")
def update(self):
self.incoming = None
for interface_id, interface in self.interfaces.items():
if interface.waiting:
interface.last_data_received = time.time()
byte = interface.read()
if byte == 0x2a:
interface.rxbuffer = []
interface.reading_escape = False
interface.reading_frame = True
if byte == 0x2b:
interface.reading_escape = True
continue
if interface.reading_escape:
byte ^= 0xff
interface.reading_escape = False
if interface.reading_frame:
interface.rxbuffer.append(byte)
if self.verify_frame(interface):
self.process_frame(interface_id, interface.rxbuffer)
interface.rxbuffer = []
def verify_frame(self, interface):
buffer_length = len(interface.rxbuffer)
if buffer_length >= 4:
# at this point we can read frame's declared size
if buffer_length >= interface.rxbuffer[3] + 5: # header (4) + payload + checksum (1)
# a frame has been read, huzzah!
interface.reading_frame = False
# checksum
if sum(interface.rxbuffer[:-1]) % 0x100 == interface.rxbuffer[-1]:
return True
else:
interface.malformed_frames += 1
self._print("broken frame received: <Cr>%s<C/>" %(interface.rxbuffer))
interface.rxbuffer = []
return False
def escape_frame(self, frame):
assert frame[0] == 0x2a
frame_escaped = [0x2a]
for byte in frame[1:]:
if byte in (0x2a, 0x2b, 0x11, 0x13):
frame_escaped.append(0x2b)
byte ^= 0xff
frame_escaped.append(byte)
return frame_escaped
def process_frame(self, source_interface_id, frame):
payload = frame[4:-1]
destination = frame[1]
if destination == self.my_id:
self.incoming = (frame[2], payload)
else:
if destination in self.interfaces:
self._print("routing frame to [%d]" %(destination))
self.interfaces[destination].write(self.escape_frame(frame))
else:
if source_interface_id == self.def_route:
self.destination_unknown += 1
self._print("unknown destination <Cr>%d<C/> for frame: <Cy>%s<C/>" %(destination,
repr(frame)), prefix.fail)
else:
self._print("routing frame to default route [%d]" %(self.def_route))
self.interfaces[self.def_route].write(self.escape_frame(frame))
def send_data(self, destination, data):
frame = [0x2a, destination, self.my_id, len(data)] + list(data)
frame.append(sum(frame) % 0x100)
frame = self.escape_frame(frame)
if destination in self.interfaces:
s = self.interfaces[destination]
else:
s = self.interfaces[self.def_route]
s.write(frame)
class TTL_Interface:
def __init__(self, interface="/dev/ttyUSB0", baudrate=57600):
try:
self.s = serial.Serial(interface, baudrate, timeout=1)
except serial.serialutil.SerialException:
self.s = None
self.rxbuffer = []
self.reading_escape = False
self.reading_frame = False
self.malformed_frames = 0
self.last_data_received = 0
@property
def waiting(self):
if self.s:
return self.s.inWaiting()
else:
return 0
def read(self):
if self.s:
return ord(self.s.read())
else:
return 0
def write(self, data):
print("Sending:", ''.join([hex(x)[2:].zfill(2) for x in data]))
if self.s:
self.s.write(bytes(data))

View file

@ -1,2 +0,0 @@
cdef class Managed:
pass