Skip to content

Commit 77a38e1

Browse files
committed
plumb uint64_t support all the way through the SDK
- When a uint64_t is serialized, it is bitwise & with INT64_MAX first to avoid overflowing the integer. In order to do that comparison at serialization time, the uint64_t needs to be stored as a uint64_t all the way through the python bindings into the any mapping. - the next step is to move the & operation up into the python bindings and prevent people from storing invalid numbers in OTIO files.
1 parent bcfd7bb commit 77a38e1

File tree

8 files changed

+74
-6
lines changed

8 files changed

+74
-6
lines changed

src/opentimelineio/deserialization.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,20 @@ class JSONDecoder : public OTIO_rapidjson::BaseReaderHandler<OTIO_rapidjson::UTF
3535
}
3636
}
3737

38-
bool Null() { return store(any()); }
38+
bool Null() { return store(any()); }
3939
bool Bool(bool b) { return store(any(b)); }
40-
41-
// coerce all integer types to int64_t
42-
bool Int(int i) { return store(any(static_cast<int64_t>(i))); }
43-
bool Uint(unsigned u) { return store(any(static_cast<int64_t>(u))); }
40+
41+
// coerce all integer types to int64_t...
42+
bool Int(int i) { return store(any(static_cast<int64_t>(i))); }
4443
bool Int64(int64_t i) { return store(any(static_cast<int64_t>(i))); }
45-
bool Uint64(uint64_t u) { return store(any(static_cast<int64_t>(u))); }
4644

45+
bool Uint(unsigned u) { return store(any(static_cast<int64_t>(u))); }
46+
bool Uint64(uint64_t u) {
47+
/// prevent an overflow
48+
return store(any(static_cast<int64_t>(u & 0x7FFFFFFFFFFFFFFF)));
49+
}
50+
51+
// ...and all floating point types to double
4752
bool Double(double d) { return store(any(d)); }
4853

4954
bool String(const char* str, OTIO_rapidjson::SizeType length, bool /* copy */) {

src/opentimelineio/safely_typed_any.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ any create_safely_typed_any(int64_t&& value) {
1414
return any(value);
1515
}
1616

17+
any create_safely_typed_any(uint64_t&& value) {
18+
return any(value);
19+
}
20+
1721
any create_safely_typed_any(double&& value) {
1822
return any(value);
1923
}
@@ -59,6 +63,10 @@ int64_t safely_cast_int64_any(any const& a) {
5963
return any_cast<int64_t>(a);
6064
}
6165

66+
uint64_t safely_cast_uint64_any(any const& a) {
67+
return any_cast<uint64_t>(a);
68+
}
69+
6270
double safely_cast_double_any(any const& a) {
6371
return any_cast<double>(a);
6472
}

src/opentimelineio/safely_typed_any.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION {
2727
any create_safely_typed_any(bool&&);
2828
any create_safely_typed_any(int&&);
2929
any create_safely_typed_any(int64_t&&);
30+
any create_safely_typed_any(uint64_t&&);
3031
any create_safely_typed_any(double&&);
3132
any create_safely_typed_any(std::string&&);
3233
any create_safely_typed_any(RationalTime&&);
@@ -39,6 +40,7 @@ any create_safely_typed_any(SerializableObject*);
3940
bool safely_cast_bool_any(any const& a);
4041
int safely_cast_int_any(any const& a);
4142
int64_t safely_cast_int64_any(any const& a);
43+
uint64_t safely_cast_uint64_any(any const& a);
4244
double safely_cast_double_any(any const& a);
4345
std::string safely_cast_string_any(any const& a);
4446
RationalTime safely_cast_rational_time_any(any const& a);

src/opentimelineio/serialization.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Encoder {
4747
virtual void write_value(bool value) = 0;
4848
virtual void write_value(int value) = 0;
4949
virtual void write_value(int64_t value) = 0;
50+
virtual void write_value(uint64_t value) = 0;
5051
virtual void write_value(double value) = 0;
5152
virtual void write_value(std::string const& value) = 0;
5253
virtual void write_value(class RationalTime const& value) = 0;
@@ -128,6 +129,10 @@ class CloningEncoder : public Encoder {
128129
_store(any(value));
129130
}
130131

132+
void write_value(uint64_t value) {
133+
_store(any(value));
134+
}
135+
131136
void write_value(std::string const& value) {
132137
_store(any(value));
133138
}
@@ -282,6 +287,10 @@ class JSONEncoder : public Encoder {
282287
_writer.Int64(value);
283288
}
284289

290+
void write_value(uint64_t value) {
291+
_writer.Uint64(value);
292+
}
293+
285294
void write_value(std::string const& value) {
286295
_writer.String(value.c_str());
287296
}
@@ -394,6 +403,7 @@ void SerializableObject::Writer::_build_dispatch_tables() {
394403
wt[&typeid(bool)] = [this](any const& value) { _encoder.write_value(any_cast<bool>(value)); };
395404
wt[&typeid(int)] = [this](any const& value) { _encoder.write_value(any_cast<int>(value)); };
396405
wt[&typeid(int64_t)] = [this](any const& value) { _encoder.write_value(any_cast<int64_t>(value)); };
406+
wt[&typeid(uint64_t)] = [this](any const& value) { _encoder.write_value(any_cast<uint64_t>(value)); };
397407
wt[&typeid(double)] = [this](any const& value) { _encoder.write_value(any_cast<double>(value)); };
398408
wt[&typeid(std::string)] = [this](any const& value) { _encoder.write_value(any_cast<std::string const&>(value)); };
399409
wt[&typeid(char const*)] = [this](any const& value) {
@@ -427,6 +437,7 @@ void SerializableObject::Writer::_build_dispatch_tables() {
427437
et[&typeid(bool)] = &_simple_any_comparison<bool>;
428438
et[&typeid(int)] = &_simple_any_comparison<int>;
429439
et[&typeid(int64_t)] = &_simple_any_comparison<int64_t>;
440+
et[&typeid(uint64_t)] = &_simple_any_comparison<uint64_t>;
430441
et[&typeid(double)] = &_simple_any_comparison<double>;
431442
et[&typeid(std::string)] = &_simple_any_comparison<std::string>;
432443
et[&typeid(char const*)] = &_simple_any_comparison<char const*>;

src/py-opentimelineio/opentimelineio-bindings/otio_bindings.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ PYBIND11_MODULE(_otio, m) {
9797
.def(py::init([](bool b) { return new PyAny(b); }))
9898
.def(py::init([](int i) { return new PyAny(i); }))
9999
.def(py::init([](int64_t i) { return new PyAny(i); }))
100+
.def(py::init([](uint64_t i) { return new PyAny(i); }))
100101
.def(py::init([](double d) { return new PyAny(d); }))
101102
.def(py::init([](std::string s) { return new PyAny(s); }))
102103
.def(py::init([](py::none) { return new PyAny(); }))

src/py-opentimelineio/opentimelineio-bindings/otio_tests.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "opentimelineio/serializableObject.h"
55
#include "opentimelineio/serializableObjectWithMetadata.h"
66
#include "opentimelineio/serializableCollection.h"
7+
#include "opentimelineio/timeline.h"
78
#include "otio_utils.h"
89

910
namespace py = pybind11;
@@ -130,6 +131,26 @@ void otio_xyzzy(std::string msg) {
130131
/* used as a debugger breakpoint */
131132
}
132133

134+
/// test the behavior of big integers in OTIO
135+
bool test_big_uint() {
136+
int64_t some_int = 4;
137+
uint64_t number_base = INT64_MAX;
138+
uint64_t giant_number = number_base + some_int;
139+
140+
SerializableObjectWithMetadata* so = new SerializableObjectWithMetadata();
141+
142+
so->metadata()["giant_number"] = giant_number;
143+
144+
bool result = true;
145+
146+
if (any_cast<uint64_t>(so->metadata()["giant_number"]) != giant_number) {
147+
return false;
148+
}
149+
150+
so->possibly_delete();
151+
return true;
152+
}
153+
133154
void otio_tests_bindings(py::module m) {
134155
TypeRegistry& r = TypeRegistry::instance();
135156
r.register_type<TestObject>();
@@ -146,4 +167,5 @@ void otio_tests_bindings(py::module m) {
146167
test.def("bash_retainers2", &test_bash_retainers2);
147168
test.def("gil_scoping", &test_gil_scoping);
148169
test.def("xyzzy", &otio_xyzzy);
170+
test.def("test_big_uint", &test_big_uint);
149171
}

src/py-opentimelineio/opentimelineio-bindings/otio_utils.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,19 @@ py::object plain_int(int64_t i) {
3939
return py::reinterpret_steal<py::object>(p);
4040
}
4141

42+
py::object plain_uint(uint64_t i) {
43+
PyObject *p = PyLong_FromUnsignedLongLong(i);
44+
return py::reinterpret_steal<py::object>(p);
45+
}
46+
4247
void _build_any_to_py_dispatch_table() {
4348
auto& t = _py_cast_dispatch_table;
4449

4550
t[&typeid(void)] = [](any const& /* a */, bool) { return py::none(); };
4651
t[&typeid(bool)] = [](any const& a, bool) { return py::cast(safely_cast_bool_any(a)); };
4752
t[&typeid(int)] = [](any const& a, bool) { return plain_int(safely_cast_int_any(a)); };
4853
t[&typeid(int64_t)] = [](any const& a, bool) { return plain_int(safely_cast_int64_any(a)); };
54+
t[&typeid(uint64_t)] = [](any const& a, bool) { return plain_uint(safely_cast_uint64_any(a)); };
4955
t[&typeid(double)] = [](any const& a, bool) { return py::cast(safely_cast_double_any(a)); };
5056
t[&typeid(std::string)] = [](any const& a, bool) { return py::cast(safely_cast_string_any(a)); };
5157
t[&typeid(RationalTime)] = [](any const& a, bool) { return py::cast(safely_cast_rational_time_any(a)); };

tests/test_timeline.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ def test_big_integers(self):
5757
self.assertTrue(result.tracks[0][0].metadata["foobar"]["toobig"] > 0)
5858
self.assertTrue(result.tracks[0][0].metadata["foobar"]["verybig"] > 0)
5959

60+
def test_big_unsigned_integer_overflow(self):
61+
test_tl = otio.schema.Timeline()
62+
some_number = 4
63+
num_base = 0x7FFFFFFFFFFFFFFF
64+
some_even_bigger_number = num_base + some_number
65+
test_tl.metadata['big_uint'] = some_even_bigger_number
66+
self.assertEqual(test_tl.metadata['big_uint'], some_even_bigger_number)
67+
68+
serialized = otio.adapters.write_to_string(test_tl, 'otio_json')
69+
result = otio.adapters.read_from_string(serialized, 'otio_json')
70+
71+
self.assertEqual(result.metadata['big_uint'], some_number - 1)
72+
6073
def test_range(self):
6174
track = otio.schema.Track(name="test_track")
6275
tl = otio.schema.Timeline("test_timeline", tracks=[track])

0 commit comments

Comments
 (0)