Math Adapters: Host Vector Types
A math adapter declares that one of the host's own vector, point or color structs is a math::vec3 (or another of the language's math types). That C++ type then crosses every binding as the script's math value, never as a class instance: parameters, return values, bound members, reflected properties and std::vectors of it. Script gets the operators, the math:: library and value semantics; the host keeps its type.
Adapting a type
One macro per type at global scope, in the header that defines the type, so every translation unit that binds it sees the adapter. It names the type (in parentheses, so template commas pass through) and the members that hold its components, in order:
#pragma once
#include <jaiscript/math/adapter.hpp>
namespace game {
struct Vec3 {
float x = 0, y = 0, z = 0;
friend bool operator==(const Vec3&, const Vec3&) = default;
};
struct Cell { int col = 0, row = 0; };
struct Rgba { float R = 1, G = 1, B = 1, A = 1; };
}
JAI_BIND_VEC3((game::Vec3), x, y, z)
JAI_BIND_IVEC2((game::Cell), col, row)
JAI_BIND_COLOR((game::Rgba), R, G, B, A)| Macro | Script type | Component members |
|---|---|---|
JAI_BIND_VEC2(T, x, y) | math::vec2 | two, converted to and from float |
JAI_BIND_VEC3(T, x, y, z) | math::vec3 | three, float |
JAI_BIND_VEC4(T, x, y, z, w) | math::vec4 | four, float |
JAI_BIND_IVEC2(T, x, y) | math::ivec2 | two, converted to and from int32 |
JAI_BIND_IVEC3(T, x, y, z) | math::ivec3 | three, int32 |
JAI_BIND_COLOR(T, r, g, b, a) | math::color | four channels, float |
Member names are the type's own (col, row; R, G, B, A). Each converts with a static_cast to the component type and back, so a double or int member works. JaiScript's own structs, jai::vec2 through jai::color in <jaiscript/math/types.hpp>, are adapted already.
Functions
An adapted type is an ordinary parameter and return type for add_function and for dynamic_binder methods and constructors:
using game::Vec3;
using game::Cell;
using game::Rgba;
eng->add_function("scale", [](Vec3 p, float k) { return Vec3{p.x * k, p.y * k, p.z * k}; });
eng->add_function("nudge", [](Vec3& p) { p.x += 100; }); // written back into the script's vector
eng->add_function("neighbor", [](Cell c) { return Cell{c.col + 1, c.row}; });
eng->add_function("faded", [](Rgba c) { return Rgba{c.R, c.G, c.B, c.A * 0.5f}; });
eng->add_function("make_path", [](int n) {
std::vector<Vec3> out;
for (int i = 0; i < n; ++i) { out.push_back(Vec3{float(i), float(i) * 2, 1}); }
return out; // arrives as array<math::vec3>
});
eng->add_function("path_length", [](std::vector<Vec3> path) { return static_cast<int64_t>(path.size()); });
jai::dynamic_binder<Body>(*eng, "Body")
.property("pos", &Body::pos)
.property("tint", &Body::tint)
.property("path", &Body::path)
.method("moved", &Body::moved);var p = scale(math::vec3(1, 2, 3), 2);
print(p, " ", type_of(p));
math::vec3 v = math::vec3(1, 2, 3);
nudge(v);
print(v);
print(neighbor(math::ivec2(4, 7)), " ", neighbor(math::vec2(4.9, 7.2))); // a vec2 converts like a typed slot
print(faded(math::color(1, 0, 0)));
var path = make_path(3);
print(path.join(" "), " ", type_of(path[0]));
array<math::vec3> pts = [math::vec3(1, 2, 3), math::vec3(4, 5, 6)];
print(path_length(pts));
try { scale(5, 2); } catch (e) { print(e); }(2.0, 4.0, 6.0) math::vec3
(101.0, 2.0, 3.0)
(5, 7) (5, 7)
(1.0, 0.0, 0.0, 0.5)
(0.0, 0.0, 1.0) (1.0, 2.0, 1.0) (2.0, 4.0, 1.0) math::vec3
2
No overload of 'scale' takes (int, int)| C++ signature | what crosses |
|---|---|
T, const T& parameter | the script vector, converted as a typed slot of that math type converts: a vec2 reaches an ivec2 parameter truncated toward zero. A scalar or a vector of other dimensions finds no overload. |
T& parameter | the callee's final value is written back into the script lvalue (a variable, a field, an element, a bound C++ member), as every host T& does (Embedding Quickstart). A temporary is passed as a copy. |
T return | arrives as the math type (type_of says math::vec3) |
std::vector<T> by value or returned | converts from and to the packed array<math::vec3> buffer |
std::vector<T>& | the callee's result is written back |
const std::vector<T>& | rejected at compile time: it would bind a converted copy |
Bound members
A member bound with .property reads as the math value. Whole-vector, component and compound stores all reach the C++ member; a read is a copy.
struct Body {
game::Vec3 pos;
game::Rgba tint;
std::vector<game::Vec3> path;
game::Vec3 moved(game::Vec3 d) const { return {pos.x + d.x, pos.y + d.y, pos.z + d.z}; }
};var b = Body();
b.pos = math::vec3(1, 2, 3);
b.pos.x += 4; // a component store reaches the C++ member
b.tint.g = 0.5;
print(b.pos, " ", type_of(b.pos), " ", b.tint);
print(b.moved(math::vec3(1, 1, 1)));
math::vec3 copy = b.pos; // a read is a copy
copy.x = 0;
print(b.pos);
void halve(float& c) { c /= 2; }
halve(b.pos.y); // a member's component binds float&
b.path = [math::vec3(1, 2, 3), math::vec3(4, 5, 6)];
print(b.pos, " ", b.path.size());(5.0, 2.0, 3.0) math::vec3 (1.0, 0.5, 1.0, 1.0)
(6.0, 3.0, 4.0)
(5.0, 2.0, 3.0)
(5.0, 1.0, 3.0) 2Reflected properties
A JAI_PROPERTY of an adapted type (dynamic_binder) surfaces as its math type with no per-property binding, and every kind of store assigns through the property:
#include <jaiscript/properties/property_manager.hpp>
#include <jaiscript/properties/observable_property.hpp>
#include <jaiscript/properties/macros.hpp>
#include <jaiscript/properties/property_serialization.hpp>
class Marker : public jai::property_owner<Marker> {
public:
JAI_PROPERTY((game::Vec3), position);
JAI_OBSERVABLE_PROPERTY((game::Vec3), velocity);
JAI_PROPERTY((game::Rgba), tint);
};jai::dynamic_binder<Marker>(*eng, "Marker");
auto marker = std::make_shared<Marker>();
int changes = 0;
auto watch = marker->velocity.on_change.connect([&](const game::Vec3&, const game::Vec3&) { ++changes; });
eng->add_global("marker", eng->make_object(marker));
eng->execute(R"(
print(type_of(marker.position), " ", marker.tint);
marker.position = math::vec3(1, 2, 3);
marker.position.x += 4;
marker.velocity.y = 2;
marker.velocity = math::vec3(1, 1, 1);
marker.tint.a = 0.5;
)");
std::cout << "position " << marker->position->x << " " << marker->position->y << " " << marker->position->z << "\n";
std::cout << "velocity changes " << changes << ", tint alpha " << marker->tint->A << "\n";math::vec3 (1.0, 1.0, 1.0, 1.0)
position 5 2 3
velocity changes 2, tint alpha 0.5An observable property notifies on each store that changes the value, compared with the host type's operator== (the component store and the whole store above: two changes). A type without operator== still stores, but never raises on_change.
Values from C++
make_value makes the math value from a host value, and as<T>() reads one back:
jai::script_value v = eng->make_value(game::Vec3{1, 2, 3}); // a math::vec3 value
eng->add_global("spawn_point", v);
game::Vec3 back = eng->execute("spawn_point * 2").as<game::Vec3>(); // and back to the host type
std::cout << v.to_string() << " -> " << back.x << " " << back.y << " " << back.z << "\n";(1.0, 2.0, 3.0) -> 2 4 6