This is the multi-page printable view of this section. Click here to print...

Return to the regular view of this page

As of 2025-07-24

External Libraries

External Libraries
This section introduces external libraries that can be used with MWX.
  • EASTL - EASTL is a C++ standard library by Electronic Arts. It provides containers that operate with fixed-length memory, making it well-suited for use in embedded microcontrollers. The TWELITE STAGE SDK can be built with it without any special configuration.

1 - EASTL

Lightweight template library
EASTL is a standard template library (containers and algorithms) maintained by Electronic Arts, implemented in the style of C++ STL (Standard Template Library). However, it was developed for the highly constrained environment of game console development, so it provides containers and algorithms designed for environments with strict memory handling constraints.

This library makes EASTL available within TWENET.

Below is an example of using a fixed-length array and applying a sorting algorithm.

The features of EASTL are as follows.

  • Containers with fixed memory allocation (fixed_): You can declare containers with a fixed number of elements without dynamic allocation. If declared globally, the memory area is allocated at compile time; if declared locally, it is allocated on the stack and available within that scope.
  • Intrusive containers: While regular containers can store arbitrary data structures, intrusive containers require your data structure to inherit from a special base class, which maintains link information for the container. Each element is dedicated to that container, but for lists and map structures, this is highly memory efficient. (Reference: Intrusive and non-intrusive containers)

The development motivation and more are described in a 2007 article: EASTL (open-std.org). (Related articles: A glimpse into the state of game software development through EASTL Part 1, Part 2)

Usage in TWENET

Please note the following.

We have not comprehensively tested the operation of this library. Please conduct your own operation verification. We are also unable to respond to inquiries about how to use EASTL. Please refer to resources and source code provided by the distributor.

  • Uses EASTL 3.07 (2018/1/31), the last version that can be compiled with C++11.
  • The following libraries are not included:
    • test/packages/EAAssert, source/assert.cpp
    • test/packages/EATest
    • test/packages/EAThread, source/thread_support.cpp
  • Test code in test/source has not been ported.
  • For sprintf-related functions, only EA::StdC::Vsnprintf(char8_t*, ...) is resolved by calling vsnprintf_() in the printf.h library.

How to Embed and Compile

You can use EASTL when writing Act scripts.

The required include paths and library additions for the TWELITE development environment are already set. Please include the library headers in your code as needed.

#include <TWELITE>
#include <EASTL/fixed_string.h>

using namespace eastl;
using tstr128 = fixed_string<char, 127 + 1, false>;

void setup() {
    tstr128 s1;
    s1 = "Hello World";
    Serial << s1.c_str();
}
void loop() {
    ;
}

Embedding Details

Compiling the library and setting the include paths have already been done under the MWSDK/TWENET directories, but the internal settings are described below.

  • Compile the code in EASTL/source into a library archive (libEASTL.a). This library must be referenced at link time.
  • Add the following include paths at compile time.

If you set $(PATH_EASTL) as the EASTL directory, the include paths are as follows:

-I$(PATH_EASTL)/include
-I$(PATH_EASTL)/test/packages/EAAssert/include
-I$(PATH_EASTL)/test/packages/EABase/include/Common
-I$(PATH_EASTL)/test/packages/EAMain/include
-I$(PATH_EASTL)/test/packages/EAStdC/include
-I$(PATH_EASTL)/test/packages/EATest/include
-I$(PATH_EASTL)/test/packages/EAThread/include

Coding Notes

About std:: and eastl::

The MWX library itself uses the standard library in the std:: namespace.

The standard library (std::) and EASTL (eastl::) both define items with the same names and functions. Sometimes they can coexist, but sometimes using both will cause errors. In general, use the definitions within EASTL for EASTL objects (for example, storing an eastl::fixed_string in a std::unique_ptr will result in a compiler error).

Also, be careful about name collisions when using statements like using namespace std;.

Global Object Initialization 1 (Placement new)

In TWENET development, due to compiler restrictions, constructors for globally declared objects are not executed. The memory area for a globally declared object is only zero-initialized. If you run the code as is, it will usually hang due to null pointer access.

To initialize such objects, use placement new.

#include <TWELITE>
#include <EASTL/fixed_string.h>

using namespace eastl;
using tstr128 = fixed_string<char, 127 + 1, false>;

    tstr128 g_str1; // constructor is NOT called! needs to be initialized before use.

void setup() {
    (void) new ((void*)&g_str1) tstr128("Hello World");
    Serial << g_str1.c_str();
}

The placement new code can look a bit messy, so a helper function mwx::pnew() is provided. The previous example can be rewritten as follows:

(void) new ((void*)&g_str1) tstr128("Hello World");
// ↓
mwx::pnew(g_str1, "Hello World");

Note: The second and subsequent arguments are variable and are passed directly to the constructor.

Global Object Initialization 2 (unique_ptr)

Another way to initialize global objects is to use unique_ptr (std::unique_ptr reference). unique_ptr exists in both std:: and eastl::, but for EASTL classes, use the eastl:: version.

Call .reset() at the timing of initialization as shown below.

#include <TWELITE>
#include <EASTL/unique_ptr.h>
#include <EASTL/fixed_string.h>

using namespace eastl;
using tstr128 = fixed_string<char, 127 + 1, false>;

eastl::unique_ptr<tstr128> uq_str1;

void setup() {
	uq_str1.reset(new tstr128("Hello World"));
    if (uq_str1) { // true: object is stored.
        Serial << uq_str1->c_str();
    }
}

About intrusive Containers

The following is an example of defining an element for intrusive_list. The only member is int mX.

struct IntNode : public eastl::intrusive_list_node {
    int mX;
    IntNode(int x = 0) : mX(x) { }
        // no need to call super class's constructor eastl::intrusive_list_node()
};

inline bool operator<(const IntNode& a, const IntNode& b) { return a.mX < b.mX; }
inline bool operator>(const IntNode& a, const IntNode& b) { return a.mX > b.mX; }

Elements for intrusive_list must always inherit from intrusive_list_node as a base class. The base class includes link pointers to maintain the list. Here, comparison operators are also defined for use with sort and similar algorithms.

using tiList = intrusive_list<IntNode>;

void setup() {
    IntNode nodeA(5);
    IntNode nodeB(1);
    IntNode nodeC(9);
    IntNode nodeD(2);
    IntNode nodeE(4);

    tiList l; // intrusive_list body

    l.push_front(nodeA); // forming list structure
                         //   by updating link info in intrusive_list_node.
    l.push_front(nodeB);
    l.push_front(nodeC);
    l.push_front(nodeD);
    l.push_front(nodeE);

    l.sort(); // sort, using < operator
    l.sort(eastl::greater<tilist::value_type>()); // sort, using > operator
}

References

About This Sample

The EASTL license is as follows.

Modified BSD License (3-Clause BSD license) see the file LICENSE in the project root.

/*
Copyright (C) 2015 Electronic Arts Inc.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1.  Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
2.  Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
3.  Neither the name of Electronic Arts, Inc. ("EA") nor the names of
    its contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

Sample code applies MWSLA-1J/E.

Code Examples

fixed_vector

An array with a fixed maximum length (i.e., it does not expand). (*Note: mwx::smplbuf is also a fixed-length array, but it is specialized for some internal processing in the MWX library.)

#include <TWELITE>
#include <EASTL/fixed_vector.h>
#include <EASTL/sort.h>

using tvct = eastl::fixed_vector<uint16_t, 64, false>;
tvct v;

void setup() {
    mwx::pnew(v); // initialize
    v = { 3, 1, 2 ,4 }; // set initial list.

    // push and pop
    v.pop_back();   // 3, 1, 2
    v.push_back(5); // 3, 1, 2, 5

    // sort
    eastl::sort(v.begin(), v.end(), eastl::less<tvct::value_type>());
                    // 1, 2, 3, 5

    // display all
    for (auto x : v) { Serial << format(" %d", x); }
    Serial << crlf;
    // using [] operator
    for (int i = 0; i < v.size(); i++) { Serial << format(" %d", v[i]); }
}

The template arguments for fixed_vector are: first is the type, second is the maximum number of elements, and third should be false. Array operations are similar to std::vector, such as .push_back(), .pop_back(), and the [] operator.

You can also apply sorting algorithms. In the example above, eastl::sort is used in ascending order with eastl::less.

fixed_list

A list structure with a fixed maximum number of elements (see also intrusive_list).

#include <TWELITE>
#include <EASTL/fixed_list.h>
#include <EASTL/sort.h>

using tdata = eastl::pair<uint8_t, void (*)(uint8_t)>; // element data type
using tlst = eastl::fixed_list<tdata, 3, false>; // fixed_list with 3 elements.
tlst l; // list object

void setup() {
    mwx::pnew(l); // initialize (call constructor)
    // add
    if (!l.full()) l.insert(l.begin(), eastl::make_pair('A', [](uint8_t v){ Serial << format("(1:%c)", v); } ));
    if (!l.full()) l.insert(l.begin(), eastl::make_pair('B', [](uint8_t v){ Serial << format("(2:%c)", v); } ));
    if (!l.full()) l.insert(l.begin(), eastl::make_pair('C', [](uint8_t v){ Serial << format("(3:%c)", v); } ));
    if (!l.full()) l.insert(l.begin(), eastl::make_pair('D', [](uint8_t v){ Serial << format("(4:%c)", v); } )); // fails
    Serial << crlf << "init: "; for(auto &x: l) x.second(x.first);
    // find & erase
    auto p = eastl::find_if(l.begin(), l.end(), [](tdata& x) { return (x.first == 'B'); } );
    if (p != l.end()) l.erase(p);
    Serial << crlf << "find&erase: "; for(auto &x: l) x.second(x.first);
    // append
    if (!l.full()) l.insert(l.end(), eastl::make_pair('D', [](uint8_t v){ Serial << format("(4:%c)", v); } ));
    Serial << crlf << "append: "; for(auto &x: l) x.second(x.first);
    // sort
    eastl::sort(l.begin(), l.end(), eastl::less<tlst::value_type>());
    Serial << crlf << "sort:"; for(auto &x: l) x.second(x.first);
    // sort reverse
    eastl::sort(l.begin(), l.end(), eastl::greater<tlst::value_type>());
    Serial << crlf << "sort(rev):"; for(auto &x: l) x.second(x.first);
}

The template arguments for fixed_list are: first is the type, second is the maximum number of elements, and third should be false. List operations are similar to std::list, such as .insert(), .erase(), etc.

In the above code, the list stores elements of eastl::pair, with the first being a uint8_t integer and the second being a function pointer void (*)(uint8_t). Lambdas are used directly in the code. The line x.second(x.first); means calling the function stored in second with the value of first.

You can search for elements using eastl::find_if and sort using bubble_sort.

intrusive_list

While regular lists can hold arbitrary data structures as elements, intrusive_list attaches specific data to elements and uses that data to build the structure.

In the following example, the element type for the intrusive_list must inherit from eastl::intrusive_list_node. eastl::intrusive_list_node is an extension that allows storing pointers to previous and next elements.

#include <TWELITE>
#include <EASTL/fixed_vector.h>
#include <EASTL/intrusive_list.h>
#include <EASTL/unique_ptr.h>

// list element of intrusive_list.
struct IntNode : public eastl::intrusive_list_node {
    int mX;
    IntNode(int x = 0) : mX(x) { }
};
inline bool operator>(const IntNode& a, const IntNode& b) { return a.mX > b.mX; } // for sort

using tpool = eastl::fixed_vector<IntNode, 16, false>;
using tlst = eastl::intrusive_list<IntNode>;

tpool pool; // instance pool.
tlst l; // list object

void setup() {
    mwx::pnew(pool); // prepare instances
    mwx::pnew(l); // initialize (call constructor)

    pool.resize(5); // create 5 instances in pool

    // insert an IntNode element into List.
    int i = 0;
    pool[i].mX = 5; l.push_front(pool[i]); i++;
    pool[i].mX = 1; l.push_front(pool[i]); i++;
    pool[i].mX = 2; l.push_front(pool[i]); i++;
    pool[i].mX = 4; l.push_front(pool[i]); i++;
    pool[i].mX = 3; l.push_front(pool[i]); i++;
    Serial << crlf << "init: ";
    for(auto& x : l) { Serial << format(" %d", x.mX); }

    l.remove(pool[2]);
    Serial << crlf << "remove: ";
    for(auto& x : l) { Serial << format(" %d", x.mX); }

    l.sort(eastl::greater<tlst::value_type>());
    Serial << crlf << "sort: ";
    for(auto& x : l) { Serial << format(" %d", x.mX); }
}

In this example, eastl::fixed_vector<> is used to allocate the necessary number of IntNode elements; fixed_vector itself is not required for the list. Five elements are given test values to build the intrusive_list. The example calls l.push_front() to add elements one by one to the list. In reality, instead of storing elements, the pointers in each IntNode are rewired.

Sorting is done by calling the member function l.sort().

ring_buffer

The ring buffer ring_buffer is constructed in combination with another container (in this example, fixed_vector).

#include <TWELITE>
#include <EASTL/fixed_vector.h>
#include <EASTL/bonus/ring_buffer.h>

const size_t N_RING_ELE = 4; // element max for RING BUFFER.
using tvec = eastl::fixed_vector<uint8_t, N_RING_ELE + 1, false>; // One extra element is required.
using tring = eastl::ring_buffer<uint8_t, tvec>;
tring rb;

void setup() {
    mwx::pnew(rb, N_RING_ELE);

    rb.push_front(5);
    rb.push_front(1);
    rb.push_front(4);
    rb.push_front(2);
    Serial << crlf; for (auto x : rb) Serial << format(" %d", x);
    rb.push_front(3);
    Serial << crlf; for (auto x : rb) Serial << format(" %d", x);
    rb.push_front(8);
    Serial << crlf; for (auto x : rb) Serial << format(" %d", x);
    rb.push_front(9);
    Serial << crlf; for (auto x : rb) Serial << format(" %d", x);

    Serial << crlf << format("back=%d", rb.back()) << crlf;
    rb.pop_back();
    for (auto x : rb) Serial << format(" %d", x);
}

The definition of ring_buffer is a combination of the element type and its container type. The element type should have one extra element.

In the example above, .push_front() inserts an element at the front. If it overflows, the oldest element at the back is dropped. Use .back() to get the oldest element, and pop_back() to remove it.

intrusive_hash_map

A map structure is a data structure that holds key-value pairs and is designed to efficiently extract elements by key. intrusive_hash_map is implemented using the intrusive structure and hash values. The definition is a bit complicated, but memory usage is minimized.

As with intrusive_list, you must define your own element type IntNode inheriting from eastl::intrusive_hash_node_key<ElementType>. You also need to define the maximum number of hash buckets (N_BUCKET_CT). This value should be a suitable prime number according to the expected number of elements.

#include <TWELITE>
#include <EASTL/internal/intrusive_hashtable.h>
#include <EASTL/intrusive_hash_map.h>

static const unsigned N_BUCKET_CT = 7;

// intrusive element type
struct IntNode : public eastl::intrusive_hash_node_key<uint8_t> {
    using SUP = intrusive_hash_node_key;
    void (*m_func)(); // member variable is func pointer.
    IntNode(uint8_t key = 0) { SUP::mKey = key; } // key will be passed by the constructor.
};

// intrusive map type
using tmap = eastl::intrusive_hash_map<uint8_t, IntNode, N_BUCKET_CT>;

tmap mp;
IntNode nd_a, nd_b, nd_c, nd_d;

void setup() {
    mwx::pnew(mp); // initialize (call constructor)

    mwx::pnew(nd_a, 'A')->m_func = []() { Serial << "FuncA"; };
    mwx::pnew(nd_b, 'B')->m_func = []() { Serial << "FuncB"; };
    mwx::pnew(nd_c, 'C')->m_func = []() { Serial << "FuncC"; };
    mwx::pnew(nd_d, 'D')->m_func = []() { Serial << "FuncD"; };

    mp.insert(nd_a);
    mp.insert(nd_b);
    mp.insert(nd_c);
    mp.insert(nd_d);
}

void loop() {
    int c = Serial.read();
    if(c != -1) {
        Serial << crlf << '[' << uint8_t(c) << ']';
        auto&& it = mp.find(uint8_t(c));
        if (it != mp.end()) it->m_func();
    }
}

In the example above, the map key is a uint8_t character, and the map value is a function pointer. In loop(), a function is executed according to the key input.

First, the table and elements are defined as global objects, so in setup() you call mwx::pnew() to initialize the data elements (nd_a, nd_b, nd_c, nd_d) and the hash map (mp). The return value of mwx::pnew() is a pointer to the constructed object, so after initialization, you can directly assign a value (lambda) to a member variable.

Once the elements (nd_a, nd_b, nd_c, nd_d) are initialized and their values set, insert them into the map with mp.insert(nd_a) and so on.

In loop(), every time a character is input from serial, a search is performed on the hash map. Search is done with the mp.find() method, which returns an iterator; if the search fails, it returns mp.end(). If the search succeeds, you can access the found element with (*it).