CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
VantagePoints.cpp
Go to the documentation of this file.
1#include "VantagePoints.h"
2#include "PsyTime.h"
3#include "UnitTestFramework.h"
4
5namespace cmlabs {
6
8 count = 0;
9 inUseMap = NULL;
10 state = IDLE;
11 intrinsicTimescaleMS = 60000;
12 timeoutMS = 60000;
13 motionStartedTime = 0;
14}
15
18
19bool VantagePoints::init(const char* xml) {
20 if (!xml)
21 return false;
22
23 //<vantagepoints>
24 // <point id="1" label="A" data="2,2,45,6" overlap="0.1,0.3,0.5" />
25 // <point id="2" label="B" data="2,2,45,6" overlap="0.1,0.3,0.5" />
26 // <point id="3" label="C" data="2,2,45,6" overlap="0.1,0.3,0.5" />
27 //</vantagepoints>
28
29 XMLResults xmlResults;
30 XMLNode node = XMLNode::parseString(xml, "vantagepoints", &xmlResults);
31
32 // display error message (if any)
33 if (xmlResults.error != eXMLErrorNone)
34 return false;
35
36 return init(node);
37}
39 XMLNode subNode;
40 const char* str = node.getAttribute("timescale"); // in secs
41 if (str)
42 intrinsicTimescaleMS = utils::Ascii2Uint32(str) * 1000;
43
44 str = node.getAttribute("timeout"); // in secs
45 if (str)
46 timeoutMS = utils::Ascii2Uint32(str) * 1000;
47
48 count = (uint32)node.nChildNode("point");
49 if (!count) {
50 LogPrint(0, 0, 1, "No points found");
51 return false;
52 }
53
54 inUseMap = new uint32[count+1]; // don't use index 0
55 memset(inUseMap, 0, (count+1) * sizeof(uint32));
56
57 std::vector<std::string> overlapsStr;
58 std::vector<std::string>::iterator i, e;
59 PerchPoint point;
60 const char* id, *label, *data, *overlap;
61 int n = 0;
62 while (!(subNode = node.getChildNode("point", n++)).isEmpty()) {
63 if ((id = subNode.getAttribute("id")) &&
64 (label = subNode.getAttribute("label")) &&
65 (data = subNode.getAttribute("data")) &&
66 (overlap = subNode.getAttribute("overlap"))) {
67 point.id = utils::Ascii2Uint32(id);
68 if (point.id > count) {
69 LogPrint(0, 0, 1, "Point %s ID is too high: %u > %u", id, point.id, count);
70 return false;
71 }
72 if (points.find(point.id) != points.end())
73 return false;
74 point.entityID = 0;
75 point.state = FREE;
76 point.lastVisit = GetTimeNow();
77 point.label = label;
78 point.data = data;
79 overlapsStr = utils::TextListSplit(overlap, ",");
80 if (overlapsStr.size() == 0) {
81 // put equal overlap values in
82 for (uint32 n = 0; n <= count; n++) // index 1 based, so add and ignore index 0
83 point.overlaps.push_back(1);
84 }
85 else {
86 if (overlapsStr.size() != count) {
87 LogPrint(0, 0, 1, "Not enough overlaps for point %s, need %u, only found %u", id, count, overlapsStr.size());
88 return false;
89 }
90 point.overlaps.clear();
91 point.overlaps.push_back(0); // index 1 based, so add and ignore index 0
92 i = overlapsStr.begin();
93 e = overlapsStr.end();
94 while (i != e) {
95 point.overlaps.push_back(utils::Ascii2Float64((*i).c_str()));
96 i++;
97 }
98 }
99 // ensure no benefit to move to itself :-)
100 point.overlaps[point.id] = 1; // 100% overlap
101 points[point.id] = point;
102 }
103 else {
104 if (!id || !label)
105 LogPrint(0, 0, 1, "Point id or name not specified");
106 else if (!overlap)
107 LogPrint(0, 0, 1, "Point %s ID has no overlap specified", point.id);
108 else if (!data)
109 LogPrint(0, 0, 1, "Point %s ID has no data specified", point.id);
110 return false;
111 }
112 }
113 return true;
114}
115
116
117const char* VantagePoints::getPointData(uint32 id) {
118 if (!count || !id || (id > count))
119 return NULL;
120 PerchPoint* perch = &(points[id]);
121 return perch->data.c_str();
122}
123
124const char* VantagePoints::getPointLabel(uint32 id) {
125 if (!count || !id || (id > count))
126 return NULL;
127 PerchPoint* perch = &(points[id]);
128 return perch->label.c_str();
129}
130
131
132uint32 VantagePoints::reserveNextPoint(uint32 entityID) {
133 if (!count || (motionStartedTime && (GetTimeAgeMS(motionStartedTime) < timeoutMS)))
134 return 0;
135
136 uint32 n;
137 double bestVal, val;
138 PerchPoint* perch = NULL;
139
140 uint32 ownPerch = 0, bestPerch = 0, occPerch = 0, eID;
141 if (state == IDLE)
142 bestPerch = 1;
143 else {
144 // find own current perch and any occupied perch
145 for (n = 1; n <= count; n++) {
146 if (!(eID = inUseMap[n]))
147 continue;
148 perch = &(points[n]);
149 if ((perch->state == RESERVED) && (GetTimeAgeMS(perch->lastVisit) > timeoutMS)) {
150 perch->state = FREE;
151 perch->entityID = 0;
152 inUseMap[perch->id] = 0;
153 continue;
154 }
155 occPerch = n;
156 if (!ownPerch && (eID == entityID)) {
157 ownPerch = n;
158 continue;
159 }
160 }
161 if (!occPerch)
162 bestPerch = 1;
163 else {
164 // go find best unoccupied perch from there
165 if (ownPerch)
166 perch = &(points[ownPerch]);
167 else
168 perch = &(points[occPerch]);
169 bestVal = 0;
170 for (n = 1; n <= count; n++) {
171 if (!inUseMap[n]) {
172 val = 1 - perch->overlaps[n];
173 if (intrinsicTimescaleMS)
174 val *= ((double)GetTimeAgeMS((&(points[n]))->lastVisit) / intrinsicTimescaleMS);
175 if (val > bestVal) {
176 bestVal = val;
177 bestPerch = n;
178 }
179 }
180 }
181 }
182 }
183
184 if (!bestPerch)
185 return 0;
186
187 uint64 now = GetTimeNow();
188
189 if (ownPerch) {
190 perch = &(points[ownPerch]);
191 perch->state = FREE;
192 perch->entityID = 0;
193 perch->lastVisit = now;
194 inUseMap[perch->id] = 0;
195 }
196
197 perch = &(points[bestPerch]);
198 perch->state = RESERVED;
199 perch->entityID = entityID;
200 perch->lastVisit = now;
201 inUseMap[perch->id] = entityID;
202 state = INMOTION;
203 motionStartedTime = now;
204 return perch->id;
205}
206
207bool VantagePoints::occupyPoint(uint32 id, uint32 entityID) {
208 if (!count || !id || (id > count) || !entityID)
209 return false;
210
211 PerchPoint* perch = &(points[id]);
212 if (perch->state != RESERVED)
213 return false;
214 if (perch->entityID != entityID)
215 return false;
216
217 perch->state = OCCUPIED;
218 state = SETTLED;
219 motionStartedTime = 0;
220 return true;
221}
222
223bool VantagePoints::freePoint(uint32 id, uint32 entityID) {
224 if (!count || !id || (id > count) || !entityID)
225 return false;
226
227 PerchPoint* perch = &(points[id]);
228 if (perch->state != RESERVED)
229 return false;
230 if (perch->entityID != entityID)
231 return false;
232
233 inUseMap[id] = 0;
234 perch->state = FREE;
235 state = SETTLED;
236 motionStartedTime = 0;
237 return true;
238}
239
240
241
243 unittest::progress(0, "init from XML");
244
245 // 1. Initialisation from XML. Three points, overlap row length == count (3).
246 const char* xml =
247 "<vantagepoints timescale=\"60\" timeout=\"60\">\n"
248 " <point id=\"1\" label=\"A\" data=\"2,2,45,6\" overlap=\"1.0,0.3,0.5\" />\n"
249 " <point id=\"2\" label=\"B\" data=\"3,3,90,7\" overlap=\"0.3,1.0,0.2\" />\n"
250 " <point id=\"3\" label=\"C\" data=\"4,4,30,8\" overlap=\"0.5,0.2,1.0\" />\n"
251 "</vantagepoints>\n";
252
253 {
254 VantagePoints vp;
255 if (!vp.init(xml)) { unittest::fail("VantagePoints test: init from XML failed"); return false; }
256 if (vp.count != 3) { unittest::fail("VantagePoints test: count != 3 (got %u)", vp.count); return false; }
257 if (vp.state != IDLE) { unittest::fail("VantagePoints test: initial state != IDLE"); return false; }
258
259 // 2. Point accessors
260 unittest::progress(20, "point accessors");
261 const char* lbl = vp.getPointLabel(1);
262 if (!lbl || strcmp(lbl, "A") != 0) { unittest::fail("VantagePoints test: getPointLabel(1) != A"); return false; }
263 const char* dat = vp.getPointData(2);
264 if (!dat || strcmp(dat, "3,3,90,7") != 0) { unittest::fail("VantagePoints test: getPointData(2) mismatch"); return false; }
265 // out-of-range / zero id must return NULL
266 if (vp.getPointLabel(0) != NULL) { unittest::fail("VantagePoints test: getPointLabel(0) not NULL"); return false; }
267 if (vp.getPointData(99) != NULL) { unittest::fail("VantagePoints test: getPointData(99) not NULL"); return false; }
268
269 // 3. Reserve -> occupy lifecycle.
270 unittest::progress(40, "reserve and occupy");
271 uint32 entity = 1001;
272 uint32 reserved = vp.reserveNextPoint(entity);
273 // From IDLE the implementation always reserves point 1.
274 if (reserved != 1) { unittest::fail("VantagePoints test: first reserve expected id 1 (got %u)", reserved); return false; }
275 if (vp.state != INMOTION) { unittest::fail("VantagePoints test: state != INMOTION after reserve"); return false; }
276
277 // While in motion (motionStartedTime set, within timeout) a new reserve must back off.
278 if (vp.reserveNextPoint(2002) != 0) { unittest::fail("VantagePoints test: reserve during motion should return 0"); return false; }
279
280 // Occupy with wrong entity must fail.
281 if (vp.occupyPoint(reserved, 9999)) { unittest::fail("VantagePoints test: occupy with wrong entity succeeded"); return false; }
282 // Occupy with correct entity must succeed and settle.
283 if (!vp.occupyPoint(reserved, entity)) { unittest::fail("VantagePoints test: occupy with correct entity failed"); return false; }
284 if (vp.state != SETTLED) { unittest::fail("VantagePoints test: state != SETTLED after occupy"); return false; }
285 // Occupying an already-occupied (non-RESERVED) point must fail.
286 if (vp.occupyPoint(reserved, entity)) { unittest::fail("VantagePoints test: re-occupy of OCCUPIED point succeeded"); return false; }
287 }
288
289 // 4. Reserve -> free lifecycle (free only works on a RESERVED point).
290 unittest::progress(60, "reserve and free");
291 {
292 VantagePoints vp;
293 if (!vp.init(xml)) { unittest::fail("VantagePoints test: re-init failed"); return false; }
294 uint32 entity = 7;
295 uint32 reserved = vp.reserveNextPoint(entity);
296 if (!reserved) { unittest::fail("VantagePoints test: reserve returned 0"); return false; }
297 // free with wrong entity must fail.
298 if (vp.freePoint(reserved, 1234)) { unittest::fail("VantagePoints test: free with wrong entity succeeded"); return false; }
299 // free with correct entity on a RESERVED point must succeed.
300 if (!vp.freePoint(reserved, entity)) { unittest::fail("VantagePoints test: free with correct entity failed"); return false; }
301 // freeing a now-FREE point must fail (not RESERVED).
302 if (vp.freePoint(reserved, entity)) { unittest::fail("VantagePoints test: free of FREE point succeeded"); return false; }
303 }
304
305 // 5. Edge / robustness: uninitialised instance and bad XML.
306 unittest::progress(80, "edge cases");
307 {
308 VantagePoints vp;
309 if (vp.count != 0) { unittest::fail("VantagePoints test: fresh count != 0"); return false; }
310 if (vp.reserveNextPoint(1) != 0) { unittest::fail("VantagePoints test: reserve on empty != 0"); return false; }
311 if (vp.occupyPoint(1, 1)) { unittest::fail("VantagePoints test: occupy on empty succeeded"); return false; }
312 if (vp.freePoint(1, 1)) { unittest::fail("VantagePoints test: free on empty succeeded"); return false; }
313 if (vp.getPointData(1) != NULL) { unittest::fail("VantagePoints test: getPointData on empty not NULL"); return false; }
314 if (vp.init((const char*)NULL)) { unittest::fail("VantagePoints test: init(NULL) succeeded"); return false; }
315 }
316
317 // 6. Throughput metric: repeated reserve/occupy/free cycles.
318 unittest::progress(90, "throughput");
319 {
320 const uint32 iters = 2000;
321 uint32 ok = 0;
322 uint64 t0 = GetTimeNow();
323 for (uint32 i = 0; i < iters; i++) {
324 VantagePoints vp;
325 if (!vp.init(xml))
326 continue;
327 uint32 id = vp.reserveNextPoint(i + 1);
328 if (id && vp.occupyPoint(id, i + 1))
329 ok++;
330 }
331 double us = (double)(GetTimeNow() - t0);
332 if (ok != iters) { unittest::fail("VantagePoints test: throughput loop incomplete (%u/%u)", ok, iters); return false; }
333 if (us > 0)
334 unittest::metric("reserve_occupy_throughput", (double)iters / us * 1e6, "ops/s", true);
335 unittest::detail("completed %u reserve/occupy cycles in %.0f us", iters, us);
336 }
337
338 unittest::progress(100, "done");
339 return true;
340}
341
344 "Vantage point reserve/occupy/free lifecycle and XML init", "data");
345}
346
347} // namespace cmlabs
CMSDK time: µs-resolution 64-bit timestamps and the Time Mapping Constant (TMC).
Small, dependency-free unit test harness used by all CMSDK object tests.
#define LogPrint
Definition Utils.h:313
Management of a fixed set of spatial "perch" points that entities can reserve, occupy and free.
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
VantageState state
Current system state.
bool occupyPoint(uint32 id, uint32 entityID)
Mark a reserved point as occupied.
static bool UnitTest()
Self test.
const char * getPointData(uint32 id)
Opaque data string attached to a point in the XML.
const char * getPointLabel(uint32 id)
Label of a point.
uint32 count
Number of perch points.
uint32 reserveNextPoint(uint32 entityID)
Reserve the best available (least-overlapping free) point for an entity.
bool freePoint(uint32 id, uint32 entityID)
Release a point held by entityID.
bool init(const char *xml)
Parse the configuration from XML text.
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
Definition PsyTime.cpp:69
int32 GetTimeAgeMS(uint64 t)
Age of a timestamp relative to now, in milliseconds.
Definition PsyTime.cpp:35
uint32 Ascii2Uint32(const char *ascii, uint32 start=0, uint32 end=0)
Parse an unsigned 32-bit decimal integer from a substring.
Definition Utils.cpp:7526
std::vector< std::string > TextListSplit(const char *text, const char *split, bool keepEmpty=true, bool autoTrim=false)
Split text on a separator.
Definition Utils.cpp:6511
float64 Ascii2Float64(const char *ascii, uint32 start=0, uint32 end=0)
Parse a 64-bit float from a substring (decimal point, not locale dependent).
Definition Utils.cpp:7546
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
void Register_VantagePoints_Tests()
@ eXMLErrorNone
Definition xml_parser.h:131
One perch: id, occupancy state, occupying entity, last-visit time, label, opaque data and overlap wei...
std::vector< double > overlaps
XMLAttribute getAttribute(int i=0) const
XMLNode getChildNode(int i=0) const
int nChildNode(XMLCSTR name) const
enum XMLError error
Definition xml_parser.h:168