Tutorial 202: Implementing a Custom Backend — coordsys_cutoff

In this tutorial you will implement coordsys_cutoff: a custom coordinate system backend that short-circuits kinematic tree traversal. Along the way you will learn the two-function interface of coordsys_backend and how coordsys_generic implements a generic coordsys frobntend for any coordsys_backend.

Background

When mapCS(CS_A, CS_B) builds a transformation, it calls tf_to_WCS() on both coordinate systems. Each call walks the parent chain all the way to WCS, composing one transformation per hop.

If CS_A and CS_B share a common subtree root, those hops cancel out in the final result — they are computed only to be subtracted again. A cutoff eliminates this redundancy: it is a coordinate system that, when locked, reports the identity transformation to any caller that reaches it during traversal. Both sides of mapCS() stop at the same cutoff node, so the redundant hops are never made.

   CS_A      CS_B
      \      /
      CS_root  ← cutoff placed here
         │
        WCS

mapCS(CS_A, CS_B):
  Without cutoff: CS_A → CS_root → WCS   (3 hops per side, 6 total)
  With cutoff:    CS_A → cutoff   (1 hop per side, 2 total)

The same concept existed as a dedicated coordsys_cutoff class in Ninbot v1.0. That class was removed when coordsys gained a built-in lock/unlock mechanism. Reimplementing it as a custom backend is a good exercise in the backend interface.

The Code

#include <memory>
#include <mutex>
#include <print>
import ninbot;

//____________________________ coordsys_cutoff_backend ____________________________

template <nin::coordsys_traits CT>
class coordsys_cutoff_backend : public nin::coordsys_backend<CT>
{
public:
    explicit coordsys_cutoff_backend(nin::coordsys<CT> const& parent)
     : parent_cs { parent.linked_copy() }
    { }

    CT::tf_data_type offset(unsigned hop_limit) const override
    {
        if (active)
            return {};
        return parent_cs.tf_to_WCS(hop_limit);
    }

    std::shared_ptr<nin::coordsys_backend<CT>> clone() const override
    {
        return std::make_shared<coordsys_cutoff_backend>(parent_cs);
    }

    void lock()   { active = true;  }
    void unlock() { active = false; }

private:
    nin::coordsys<CT> parent_cs;
    bool              active = false;
};

//____________________________ coordsys_cutoff ____________________________

template <nin::coordsys_traits CT>
class coordsys_cutoff : public nin::coordsys_generic<coordsys_cutoff_backend<CT>>
{
    using Base = nin::coordsys_generic<coordsys_cutoff_backend<CT>>;
public:
    using Base::Base;

    void lock()   { this->operator->()->lock();   }
    void unlock() { this->operator->()->unlock(); }
};

//____________________________ main ____________________________

int main()
{
    using namespace nin;
    using namespace nin::R3;

    //  WCS
    //   └── CS_root  (3 m along x)
    //        ├── CS_A  (1 m along x from CS_root)
    //        └── CS_B  (0 m along x, 1 m along y from CS_root)

    position_coordsys_child CS_root {WCS,     { {3_m, 0_m, 0_m}, {} }};
    position_coordsys_child CS_A    {CS_root, { {1_m, 0_m, 0_m}, {} }};
    position_coordsys_child CS_B    {CS_root, { {0_m, 1_m, 0_m}, {} }};

    point P = {CS_A, {0_m, 0_m, 0_m}};

    std::println("P in CS_B (no cutoff):      {}", P.map_to(CS_B));

    // Rebuild the same tree, inserting a cutoff at CS_root.
    coordsys_cutoff<position_coordsys_traits> cutoff {CS_root};
    position_coordsys_child CS_A2 {cutoff, { {1_m, 0_m, 0_m}, {} }};
    position_coordsys_child CS_B2 {cutoff, { {0_m, 1_m, 0_m}, {} }};

    point P2 = {CS_A2, {0_m, 0_m, 0_m}};

    // Unlocked: cutoff passes traversal through to CS_root -> WCS as normal.
    std::println("P in CS_B2 (unlocked): {}", P2.map_to(CS_B2));

    // Locked: traversal stops at the cutoff.  The relative transformation
    // between CS_A2 and CS_B2 is the same, so the result is identical.
    {
        std::unique_lock guard {cutoff};
        std::println("P in CS_B2 (locked):   {}", P2.map_to(CS_B2));
    }  // cutoff unlocked here

    return 0;
}

Output

P in CS_B (no cutoff):      (1, -1, 0) (m)
P in CS_B2 (unlocked): (1, -1, 0) (m)
P in CS_B2 (locked):   (1, -1, 0) (m)

All three lines agree: the cutoff does not change the result, only the amount of work done to produce it.

Building the code

Save the example as cutoff.c++ alongside this CMakeLists.txt:

cmake_minimum_required(VERSION 4.0)
project(cutoff LANGUAGES CXX)

find_package(Ninbot REQUIRED)

add_executable(cutoff cutoff.c++)
target_link_libraries(cutoff PRIVATE ninbot::ninbot)

Then configure and build:

cmake -B build -G Ninja -DCMAKE_PREFIX_PATH=/path/to/ninbot/build
cmake --build build
./build/cutoff

Walkthrough

The backend interface

A coordsys_backend<CT> has two pure virtual functions:

offset(unsigned hop_limit)

Returns the transformation from this coordinate system toward WCS. A child backend typically composes its local offset with parent_cs.tf_to_WCS(hop_limit). The cutoff backend returns a default-constructed (identity) tf_data_type when locked, and delegates to the parent as normal when unlocked.

clone()

Returns a shared_ptr to a deep copy of the backend. Called by the coordsys copy constructor to implement value semantics.

template <nin::coordsys_traits CT>
class coordsys_cutoff_backend : public nin::coordsys_backend<CT>
{
public:
    explicit coordsys_cutoff_backend(nin::coordsys<CT> const& parent)
     : parent_cs { parent.linked_copy() }
    { }

    CT::tf_data_type offset(unsigned hop_limit) const override
    {
        if (active)
            return {};
        return parent_cs.tf_to_WCS(hop_limit);
    }

    std::shared_ptr<nin::coordsys_backend<CT>> clone() const override
    {
        return std::make_shared<coordsys_cutoff_backend>(parent_cs);
    }

    void lock()   { active = true;  }
    void unlock() { active = false; }

private:
    nin::coordsys<CT> parent_cs;
    bool              active = false;
};

The parent is stored as a linked_copy() so that changes to the original coordinate system are reflected in the cutoff without copying the backend.

Wrapping the backend with coordsys_generic

coordsys_generic<BE> is a thin coordinate system class that owns a backend of type BE and exposes it through operator→. It inherits from coordsys<CT>, so a coordsys_cutoff can be used anywhere a plain coordinate system is accepted — including as the parent of a coordsys_child.

Rather than a using alias, coordsys_cutoff is defined as a class that inherits from coordsys_generic and adds lock() and unlock() member functions directly. This makes it satisfy the Lockable named requirement, enabling std::unique_lock.

template <nin::coordsys_traits CT>
class coordsys_cutoff : public nin::coordsys_generic<coordsys_cutoff_backend<CT>>
{
    using Base = nin::coordsys_generic<coordsys_cutoff_backend<CT>>;
public:
    using Base::Base;

    void lock()   { this->operator->()->lock();   }
    void unlock() { this->operator->()->unlock(); }
};
coordsys_cutoff<position_coordsys_traits> cutoff {CS_root};
position_coordsys_child CS_A2 {cutoff, { {1_m, 0_m, 0_m}, {} }};
position_coordsys_child CS_B2 {cutoff, { {0_m, 1_m, 0_m}, {} }};

CS_A2 and CS_B2 are children of the cutoff, which is itself a child of CS_root.

Using the cutoff

{
    std::unique_lock guard {cutoff};
    std::println("P in CS_B2 (locked):   {}", P2.map_to(CS_B2));
}  // cutoff unlocked here

std::unique_lock calls cutoff.lock() on entry and cutoff.unlock() on scope exit, ensuring the cutoff is always unlocked even if an exception is thrown. While locked, offset() returns the identity transformation, so tf_to_WCS() stops at the cutoff node. The transformation between CS_A2 and CS_B2 is computed entirely from their local offsets relative to the cutoff, with no traversal above it.

For the simple case of locking a single subtree root, the built-in lock() on coordsys (see Tutorial 105) is sufficient and requires no custom backend. coordsys_cutoff is most useful when you need the cutoff to be a named, reusable object that is passed between functions or shared across multiple subtrees.