Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added sequences support on pyopendds [#18] #20

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion pyopendds/DataWriter.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,29 @@
from __future__ import annotations

from .Topic import Topic
from .constants import StatusKind
from .util import TimeDurationType, normalize_time_duration

from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .Publisher import Publisher


class DataWriter:
pass

def __init__(self, publisher: Publisher, topic: Topic, qos=None, listener=None):
self.topic = topic
self.qos = qos
self.listener = listener
self.publisher = publisher
publisher.writers.append(self)

from _pyopendds import create_datawriter
create_datawriter(self, publisher, topic)

def wait_for(self, status: StatusKind, timeout: TimeDurationType):
from _pyopendds import datareader_wait_for
return datareader_wait_for(self, status, *normalize_time_duration(timeout))

def write(self, sample):
return self.topic._ts_package.write(self, sample)
5 changes: 3 additions & 2 deletions pyopendds/Publisher.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from .DataWriter import DataWriter
from .Topic import Topic

from typing import TYPE_CHECKING
Expand All @@ -18,5 +19,5 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None):
from _pyopendds import create_publisher
create_publisher(self, participant)

def create_datawriter(self, topic: Topic, qos=None, listener=None):
pass
def create_datawriter(self, topic: Topic, qos=None, listener=None) -> DataWriter:
return DataWriter(self, topic, qos, listener)
54 changes: 47 additions & 7 deletions pyopendds/dev/include/pyopendds/user.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class IntegerType {

static PyObject* get_python_class()
{
return PyLong_Type;
return PyLong_FromLong(0);
Comment on lines 31 to +33
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was meant to return the class itself, not an instance.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get a compilation error without this modification.

}

static void cpp_to_python(const T& cpp, PyObject*& py)
Expand All @@ -56,13 +56,17 @@ class IntegerType {
"Integer Value is Out of Range for IDL Type", PyExc_ValueError);
}
if (value == -1 && PyErr_Occurred()) throw Exception();
cpp = value;
cpp = T(value);
}

};

typedef ::CORBA::Long i32;
template<> class Type<i32>: public IntegerType<i32> {};

typedef ::CORBA::Short i16;
template<> class Type<i16>: public IntegerType<i16> {};

// TODO: Put Other Integer Types Here

const char* string_data(const std::string& cpp)
Expand Down Expand Up @@ -90,7 +94,7 @@ class StringType {
public:
static PyObject* get_python_class()
{
return PyUnicode_Type;
return PyUnicode_FromString("");
}

static void cpp_to_python(const T& cpp, PyObject*& py, const char* encoding)
Expand All @@ -101,9 +105,16 @@ class StringType {
py = o;
}

static void python_to_cpp(PyObject* py, T& cpp)
static void python_to_cpp(PyObject* py, T& cpp, const char* encoding)
{
// TODO: Encode or Throw Unicode Error
PyObject* repr = PyObject_Str(py);
if (!repr) throw Exception();
PyObject* str = PyUnicode_AsEncodedString(repr, encoding, NULL);
if (!str) throw Exception();
const char *bytes = PyBytes_AS_STRING(str);
cpp = T(bytes);
Py_XDECREF(repr);
Py_XDECREF(str);
}
};

Expand All @@ -127,6 +138,7 @@ class TopicTypeBase {
virtual const char* type_name() = 0;
virtual void register_type(PyObject* pyparticipant) = 0;
virtual PyObject* take_next_sample(PyObject* pyreader) = 0;
virtual PyObject* write(PyObject* pywriter, PyObject* pysample) = 0;

typedef std::shared_ptr<TopicTypeBase> Ptr;
typedef std::map<PyObject*, Ptr> TopicTypes;
Expand Down Expand Up @@ -215,15 +227,15 @@ class TopicType : public TopicTypeBase {
DDS::WaitSet_var ws = new DDS::WaitSet;
ws->attach_condition(read_condition);
DDS::ConditionSeq active;
const DDS::Duration_t max_wait_time = {10, 0};
const DDS::Duration_t max_wait_time = {60, 0};
if (Errors::check_rc(ws->wait(active, max_wait_time))) {
throw Exception();
}
ws->detach_condition(read_condition);
reader_impl->delete_readcondition(read_condition);

IdlType sample;
DDS::SampleInfo info;
DDS::SampleInfo info;
if (Errors::check_rc(reader_impl->take_next_sample(sample, info))) {
throw Exception();
}
Expand All @@ -234,6 +246,34 @@ class TopicType : public TopicTypeBase {
return rv;
}

PyObject* write(PyObject* pywriter, PyObject* pysample)
{
DDS::DataWriter* writer = get_capsule<DDS::DataWriter>(pywriter);
if (!writer) throw Exception();

DataWriter* writer_impl = DataWriter::_narrow(writer);
if (!writer_impl) {
throw Exception(
"Could not narrow writer implementation", Errors::PyOpenDDS_Error());
}

IdlType rv;
Type<IdlType>::python_to_cpp(pysample, rv);

DDS::ReturnCode_t rc = writer_impl->write(rv, DDS::HANDLE_NIL);
if (Errors::check_rc(rc)) {
throw Exception();
}
// Wait for samples to be acknowledged
DDS::Duration_t timeout = { 30, 0 };
if (writer_impl->wait_for_acknowledgments(timeout) != DDS::RETCODE_OK) {
throw Exception(
"wait_for_acknowledgments error : ", Errors::PyOpenDDS_Error());
}
Comment on lines +267 to +272
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a separate operation like it is in OpenDDS, because this could be a lengthy process depending on how much stuff is being sent and to how many peers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, it's needed as a separate function on our application too.


return pysample;
}

PyObject* get_python_class()
{
return Type<IdlType>::get_python_class();
Expand Down
79 changes: 65 additions & 14 deletions pyopendds/dev/itl2py/CppOutput.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from jinja2 import Environment

from .ast import PrimitiveType, StructType, EnumType
from .ast import PrimitiveType, StructType, EnumType, SequenceType
from .Output import Output


Expand All @@ -13,6 +13,8 @@ def cpp_type_name(type_node):
return type_node.kind.name
elif isinstance(type_node, (StructType, EnumType)):
return cpp_name(type_node.name.parts)
elif isinstance(type_node, (SequenceType)):
return cpp_name(type_node.name.parts);
else:
raise NotImplementedError

Expand Down Expand Up @@ -44,33 +46,85 @@ def visit_struct(self, struct_type):
struct_to_lines = [
'Ref field_value;',
]
struct_from_lines = []
struct_from_lines = [
'Ref field_value;',
]
for field_name, field_node in struct_type.fields.items():
to_lines = []
from_lines = []
pyopendds_type = ''
is_string = isinstance(field_node.type_node, PrimitiveType) and \
field_node.type_node.is_string()
is_sequence = isinstance(field_node.type_node, SequenceType)

if is_sequence:
to_lines = [
'Ref field_elem;',
'field_value = PyList_New(0);',
'for (int i = 0; i < cpp.{field_name}.length(); i++) {{',
' {pyopendds_type} elem = cpp.{field_name}[i];',
' field_elem = nullptr;',
' Type<{pyopendds_type}>::cpp_to_python(elem',
' #ifdef CPP11_IDL',
' ()',
' #endif',
' , *field_elem' + (', "{default_encoding}"' if is_string else '') + ');',
' PyList_Append(*field_value, *field_elem);',
'}}'
]
else:
to_lines = [
'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}',
'#ifdef CPP11_IDL',
' ()',
'#endif',
' , *field_value'
+ (', "{default_encoding}"' if is_string else '') + ');',
]

to_lines = [
'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}',
'#ifdef CPP11_IDL',
' ()',
'#endif',
' , *field_value'
+ (', "{default_encoding}"' if is_string else '') + ');',
from_lines = [
'if (PyObject_HasAttrString(py, "{field_name}")) {{',
' *field_value = PyObject_GetAttrString(py, "{field_name}");',
'}}',
'if (!field_value) {{',
' throw Exception();',
'}}'
]

pyopendds_type = cpp_type_name(field_node.type_node)

if to_lines:
to_lines.extend([
'if (!field_value || PyObject_SetAttrString('
'if (!field_value || PyObject_SetAttrString(',
'py, "{field_name}", *field_value)) {{',
' throw Exception();',
'}}'
])

if from_lines:
if is_sequence:
from_lines.extend([
'cpp.{field_name}.length(PyList_Size(*field_value));',
'for (int i = 0; i < PyList_Size(*field_value); i++) {{',
' ::ContTrajSegment elem = cpp.{field_name}[i];',
' Type<{pyopendds_type}>::python_to_cpp(PyList_GetItem(*field_value, i), elem',
'#ifdef CPP11_IDL',
' ()',
'#endif',
' ' + (', "{default_encoding}"' if is_string else '') + ');',
' cpp.{field_name}[i] = elem;',
'}}'
])
else:
from_lines.extend([
'Type<{pyopendds_type}>::python_to_cpp(*field_value, cpp.{field_name}',
'#ifdef CPP11_IDL',
' ()',
'#endif',
' '
+ (', "{default_encoding}"' if is_string else '') + ');'
])

def line_process(lines):
return [''] + [
s.format(
Expand Down Expand Up @@ -107,9 +161,6 @@ def visit_enum(self, enum_type):
'args = PyTuple_Pack(1, PyLong_FromLong(static_cast<long>(cpp)));',
]),
'to_lines': '',
'from_lines': '\n'.join([
'',
'// left unimplemented'
]),
'from_lines': '',
'is_topic_type': False,
})
6 changes: 4 additions & 2 deletions pyopendds/dev/itl2py/PythonOutput.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .ast import PrimitiveType, StructType, EnumType
from .ast import PrimitiveType, StructType, EnumType, SequenceType
from .Output import Output


Expand Down Expand Up @@ -72,6 +72,8 @@ def get_python_default_value_string(self, field_type):
return type_name + '()'
elif isinstance(field_type, EnumType):
return type_name + '.' + field_type.default_member
elif isinstance(field_type, SequenceType):
return 'field(default_factory=list)'
else:
raise NotImplementedError(repr(field_type) + " is not supported")

Expand All @@ -98,4 +100,4 @@ def visit_enum(self, enum_type):
dict(name=name, value=value) for name, value in enum_type.members.items()
],
),
))
))
3 changes: 3 additions & 0 deletions pyopendds/dev/itl2py/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ def __repr__(self):
return self.repr_template(repr(self.base_type)
+ ("max " + str(self.max_count) if self.max_count else "no max"))

def repr_name(self):
if self.name:
return '::' + self.name.join('::') + '::_tao_seq_' + self.base_type + '_'

class NodeVisitor:

Expand Down
17 changes: 13 additions & 4 deletions pyopendds/dev/itl2py/itl.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def parse_string(details):


def parse_sequence(types, details):
base_type = parse_type(types, details["type"])
base_type = parse_type(types, list(types)[0])
sequence_max_count = details.get("capacity", None)
array_dimensions = details.get("size", None)
if array_dimensions is not None:
Expand All @@ -86,9 +86,16 @@ def parse_sequence(types, details):
def parse_record(types, details):
struct_type = StructType()
for field_dict in details['fields']:
struct_type.add_field(
field_dict['name'], parse_type(types, field_dict['type']),
field_dict.get('optional', False))
if 'sequence' in field_dict['type']:
sequence = parse_sequence(types, {'type': field_dict['type'], 'capacity': 1, 'size': None})
sequence.set_name(itl_name=sequence.base_type.name.itl_name)
struct_type.add_field(
field_dict['name'], sequence,
field_dict.get('optional', False))
else:
struct_type.add_field(
field_dict['name'], parse_type(types, field_dict['type']),
field_dict.get('optional', False))
return struct_type


Expand Down Expand Up @@ -132,6 +139,8 @@ def parse_type(types, details):
if details_type is str:
if details in types:
return types[details]
elif 'sequence' in details :
return parse_sequence(types, {'type':types, 'capacity': 1, 'size': None})
else:
raise ValueError("Invalid Type: " + details)
elif details_type is dict:
Expand Down
Loading