- 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.
This is the multi-page printable view of this section. Click here to print...
External Libraries
- 1: EASTL
1 - EASTL
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 callingvsnprintf_()
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
- EA Standard Template Library — Note: the library included in TWENET is for EASTL 3.07, so it may not include elements implemented or changed in later versions.
- cpprefjp - C++ Japanese Reference — While it’s in Japanese, the STL explanations are useful.
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)
.
intrusive_hash_multimap
is a multimap that allows duplicate values. Usage is almost the same as a hash map, but when searching you use .equal_range()
, and you handle a pair of iterators.
using tmap = eastl::intrusive_hash_multimap<uint8_t, IntNode, N_BUCKET_CT>;
...
// find elements by key `c'
auto ip = mp.equal_range(uint8_t(c));
for(auto&& it = ip.first; it != ip.second; it++) {
it->m_func();
}