coroutine.h
Go to the documentation of this file.
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 #ifndef STDEX_COROUTINE_H_
21 #define STDEX_COROUTINE_H_
22 
23 #ifndef STACK_LIMIT
24 #define STACK_LIMIT (1024 * 1024)
25 #endif
26 
27 #include <cstdint>
28 #include <cstring>
29 #include <cstdio>
30 #include <cassert>
31 
32 #include <string>
33 #include <vector>
34 #include <list>
35 #include <thread>
36 #include <future>
37 
38 using ::std::string;
39 using ::std::wstring;
40 
41 #ifdef _MSC_VER
42 #include <Windows.h>
43 #else
44 #if defined(__APPLE__) && defined(__MACH__)
45 #define _XOPEN_SOURCE
46 #include <ucontext.h>
47 #else
48 #include <ucontext.h>
49 #endif
50 #endif
51 
52 namespace coroutine
53 {
54 typedef unsigned routine_t;
55 
56 enum class ResumeResult
57 {
58  INVALID = -1,
59  FINISHED = -2,
60  YIELD = 0
61 };
62 
63 #ifdef _MSC_VER
64 
65 struct Routine
66 {
67  std::function<void()> func;
68  bool finished;
69  LPVOID fiber;
70 
71  Routine(std::function<void()> f)
72  {
73  func = f;
74  finished = false;
75  fiber = nullptr;
76  }
77 
78  ~Routine()
79  {
80  DeleteFiber(fiber);
81  }
82 };
83 
84 struct Ordinator
85 {
86  std::vector<Routine*> routines;
87  std::list<routine_t> indexes;
88  routine_t current;
89  size_t stack_size;
90  LPVOID fiber;
91 
92  Ordinator(size_t ss = STACK_LIMIT)
93  {
94  current = 0;
95  stack_size = ss;
96  fiber = ConvertThreadToFiber(nullptr);
97  }
98 
99  ~Ordinator()
100  {
101  for (auto& routine : routines)
102  delete routine;
103  }
104 };
105 
106 thread_local static Ordinator ordinator;
107 
108 inline routine_t create(std::function<void()> f)
109 {
110  Routine* routine = new Routine(f);
111 
112  if (ordinator.indexes.empty())
113  {
114  ordinator.routines.push_back(routine);
115  return (routine_t)ordinator.routines.size();
116  }
117  else
118  {
119  routine_t id = ordinator.indexes.front();
120  ordinator.indexes.pop_front();
121  assert(ordinator.routines[id - 1] == nullptr);
122  ordinator.routines[id - 1] = routine;
123  return id;
124  }
125 }
126 
127 inline void destroy(routine_t id)
128 {
129  Routine* routine = ordinator.routines[id - 1];
130  assert(routine != nullptr);
131 
132  delete routine;
133  ordinator.routines[id - 1] = nullptr;
134  ordinator.indexes.push_back(id);
135 }
136 
137 inline void __stdcall entry(LPVOID)
138 {
139  routine_t id = ordinator.current;
140  Routine* routine = ordinator.routines[id - 1];
141  assert(routine != nullptr);
142 
143  routine->func();
144 
145  routine->finished = true;
146  ordinator.current = 0;
147 
148  SwitchToFiber(ordinator.fiber);
149 }
150 
151 inline ResumeResult resume(routine_t id)
152 {
153  assert(ordinator.current == 0);
154 
155  Routine* routine = ordinator.routines[id - 1];
156  if (routine == nullptr)
157  return ResumeResult::INVALID;
158 
159  if (routine->finished)
160  return ResumeResult::FINISHED;
161 
162  if (routine->fiber == nullptr)
163  {
164  routine->fiber = CreateFiber(ordinator.stack_size, entry, 0);
165  ordinator.current = id;
166  SwitchToFiber(routine->fiber);
167  }
168  else
169  {
170  ordinator.current = id;
171  SwitchToFiber(routine->fiber);
172  }
173 
175 }
176 
177 inline void yield()
178 {
179  routine_t id = ordinator.current;
180  Routine* routine = ordinator.routines[id - 1];
181  if (routine == nullptr)
182  {
183  throw std::runtime_error("Error in yield of coroutine");
184  }
185 
186  ordinator.current = 0;
187  SwitchToFiber(ordinator.fiber);
188 }
189 
190 inline routine_t current()
191 {
192  return ordinator.current;
193 }
194 
195 #if 0
196 template<typename Function>
197 inline typename std::result_of<Function()>::type
198 await(Function &&func)
199 {
200  auto future = std::async(std::launch::async, func);
201  std::future_status status = future.wait_for(std::chrono::milliseconds(0));
202 
203  while (status == std::future_status::timeout)
204  {
205  if (ordinator.current != 0)
206  yield();
207 
208  status = future.wait_for(std::chrono::milliseconds(0));
209  }
210  return future.get();
211 }
212 #endif
213 
214 #if 1
215 template <typename Function>
216 inline std::result_of_t<std::decay_t<Function>()> await(Function&& func)
217 {
218  auto future = std::async(std::launch::async, func);
219  std::future_status status = future.wait_for(std::chrono::milliseconds(0));
220 
221  while (status == std::future_status::timeout)
222  {
223  if (ordinator.current != 0)
224  yield();
225 
226  status = future.wait_for(std::chrono::milliseconds(0));
227  }
228  return future.get();
229 }
230 #endif
231 
232 #else
233 
234 struct Routine
235 {
236  std::function<void()> func;
237  char* stack;
238  bool finished;
239  ucontext_t ctx;
240 
241  Routine(std::function<void()> f)
242  {
243  func = f;
244  stack = nullptr;
245  finished = false;
246  }
247 
249  {
250  delete[] stack;
251  }
252 };
253 
254 struct Ordinator
255 {
256  std::vector<Routine*> routines;
257  std::list<routine_t> indexes;
258  routine_t current;
259  size_t stack_size;
260  ucontext_t ctx;
261 
262  inline Ordinator(size_t ss = STACK_LIMIT)
263  {
264  current = 0;
265  stack_size = ss;
266  }
267 
268  inline ~Ordinator()
269  {
270  for (auto& routine : routines)
271  delete routine;
272  }
273 };
274 
275 thread_local static Ordinator ordinator;
276 
277 inline routine_t create(std::function<void()> f)
278 {
279  Routine* routine = new Routine(f);
280 
281  if (ordinator.indexes.empty())
282  {
283  ordinator.routines.push_back(routine);
284  return ordinator.routines.size();
285  }
286  else
287  {
288  routine_t id = ordinator.indexes.front();
289  ordinator.indexes.pop_front();
290  assert(ordinator.routines[id - 1] == nullptr);
291  ordinator.routines[id - 1] = routine;
292  return id;
293  }
294 }
295 
296 inline void destroy(routine_t id)
297 {
298  Routine* routine = ordinator.routines[id - 1];
299  assert(routine != nullptr);
300 
301  delete routine;
302  ordinator.routines[id - 1] = nullptr;
303 }
304 
305 inline void entry()
306 {
307  routine_t id = ordinator.current;
308  Routine* routine = ordinator.routines[id - 1];
309  routine->func();
310 
311  routine->finished = true;
312  ordinator.current = 0;
313  ordinator.indexes.push_back(id);
314 }
315 
316 inline ResumeResult resume(routine_t id)
317 {
318  assert(ordinator.current == 0);
319 
320  Routine* routine = ordinator.routines[id - 1];
321  if (routine == nullptr)
322  return ResumeResult::INVALID;
323 
324  if (routine->finished)
325  return ResumeResult::FINISHED;
326 
327  if (routine->stack == nullptr)
328  {
329  //initializes the structure to the currently active context.
330  //When successful, getcontext() returns 0
331  //On error, return -1 and set errno appropriately.
332  getcontext(&routine->ctx);
333 
334  //Before invoking makecontext(), the caller must allocate a new stack
335  //for this context and assign its address to ucp->uc_stack,
336  //and define a successor context and assign its address to ucp->uc_link.
337  routine->stack = new char[ordinator.stack_size];
338  routine->ctx.uc_stack.ss_sp = routine->stack;
339  routine->ctx.uc_stack.ss_size = ordinator.stack_size;
340  routine->ctx.uc_link = &ordinator.ctx;
341  ordinator.current = id;
342 
343  //When this context is later activated by swapcontext(), the function entry is called.
344  //When this function returns, the successor context is activated.
345  //If the successor context pointer is NULL, the thread exits.
346  makecontext(&routine->ctx, reinterpret_cast<void (*)(void)>(entry), 0);
347 
348  //The swapcontext() function saves the current context,
349  //and then activates the context of another.
350  swapcontext(&ordinator.ctx, &routine->ctx);
351  }
352  else
353  {
354  ordinator.current = id;
355  swapcontext(&ordinator.ctx, &routine->ctx);
356  }
357 
359 }
360 
361 inline void yield()
362 {
363  routine_t id = ordinator.current;
364  Routine* routine = ordinator.routines[id - 1];
365  assert(routine != nullptr);
366 
367  char* stack_top = routine->stack + ordinator.stack_size;
368  char stack_bottom = 0;
369  assert(size_t(stack_top - &stack_bottom) <= ordinator.stack_size);
370 
371  ordinator.current = 0;
372  swapcontext(&routine->ctx, &ordinator.ctx);
373 }
374 
375 inline routine_t current()
376 {
377  return ordinator.current;
378 }
379 
380 template <typename Function>
381 inline typename std::result_of<Function()>::type await(Function&& func)
382 {
383  auto future = std::async(std::launch::async, func);
384  std::future_status status = future.wait_for(std::chrono::milliseconds(0));
385 
386  while (status == std::future_status::timeout)
387  {
388  if (ordinator.current != 0)
389  yield();
390 
391  status = future.wait_for(std::chrono::milliseconds(0));
392  }
393  return future.get();
394 }
395 
396 #endif
397 
398 template <typename Type>
399 class Channel
400 {
401  public:
403  {
404  _taker = 0;
405  }
406 
407  Channel(routine_t id)
408  {
409  _taker = id;
410  }
411 
412  inline void consumer(routine_t id)
413  {
414  _taker = id;
415  }
416 
417  inline void push(const Type& obj)
418  {
419  _list.push_back(obj);
420  if (_taker && _taker != current())
421  resume(_taker);
422  }
423 
424  inline void push(Type&& obj)
425  {
426  _list.push_back(std::move(obj));
427  if (_taker && _taker != current())
428  resume(_taker);
429  }
430 
431  inline Type pop()
432  {
433  if (!_taker)
434  _taker = current();
435 
436  while (_list.empty())
437  yield();
438 
439  Type obj = std::move(_list.front());
440  _list.pop_front();
441  return std::move(obj);
442  }
443 
444  inline void clear()
445  {
446  _list.clear();
447  }
448 
449  inline void touch()
450  {
451  if (_taker && _taker != current())
452  resume(_taker);
453  }
454 
455  inline size_t size()
456  {
457  return _list.size();
458  }
459 
460  inline bool empty()
461  {
462  return _list.empty();
463  }
464 
465  private:
466  std::list<Type> _list;
467  routine_t _taker;
468 };
469 
470 } // namespace coroutine
471 #endif //STDEX_COROUTINE_H_
ucontext_t ctx
Definition: coroutine.h:239
static thread_local Ordinator ordinator
Definition: coroutine.h:275
std::function< void()> func
Definition: coroutine.h:236
void yield()
Definition: coroutine.h:361
std::list< Type > _list
Definition: coroutine.h:466
std::list< routine_t > indexes
Definition: coroutine.h:257
void entry()
Definition: coroutine.h:305
routine_t create(std::function< void()> f)
Definition: coroutine.h:277
void destroy(routine_t id)
Definition: coroutine.h:296
unsigned routine_t
Definition: coroutine.h:54
Channel(routine_t id)
Definition: coroutine.h:407
std::result_of< Function()>::type await(Function &&func)
Definition: coroutine.h:381
void consumer(routine_t id)
Definition: coroutine.h:412
void push(const Type &obj)
Definition: coroutine.h:417
void push(Type &&obj)
Definition: coroutine.h:424
routine_t _taker
Definition: coroutine.h:467
std::vector< Routine * > routines
Definition: coroutine.h:256
#define STACK_LIMIT
Definition: coroutine.h:24
Routine(std::function< void()> f)
Definition: coroutine.h:241
ResumeResult resume(routine_t id)
Definition: coroutine.h:316
routine_t current()
Definition: coroutine.h:375
Ordinator(size_t ss=STACK_LIMIT)
Definition: coroutine.h:262


behaviortree_cpp
Author(s): Michele Colledanchise, Davide Faconti
autogenerated on Sat Jun 8 2019 18:04:04