├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── bin └── test.txt ├── kit.tasks ├── kit ├── args │ ├── args.cpp │ └── args.h ├── async │ ├── async.h │ ├── async_fstream.h │ ├── channel.h │ ├── mx.h │ └── task.h ├── cache │ ├── cache.h │ └── icache.h ├── factory │ ├── factory.h │ └── ifactory.h ├── freq │ ├── animation.h │ └── freq.h ├── fs │ └── fs.h ├── kit.h ├── kit_compat.h ├── log │ ├── errors.h │ ├── log.cpp │ └── log.h ├── math │ ├── angle.h │ ├── common.h │ ├── matrixops.h │ ├── matrixstack.cpp │ ├── matrixstack.h │ └── vectorops.h ├── meta │ ├── meta.h │ ├── meta.inl │ ├── schema.h │ └── schema.inl ├── net │ └── net.h └── reactive │ ├── reactive.h │ └── signal.h ├── premake5.lua ├── sg.json ├── tests ├── animation.test.cpp ├── args.test.cpp ├── async.test.cpp ├── cache.test.cpp ├── factory.test.cpp ├── fs.test.cpp ├── log.test.cpp ├── meta.test.cpp ├── mutex_wrap.test.cpp ├── net.test.cpp ├── reactive.test.cpp ├── schema.test.cpp ├── slice.test.cpp └── suite.cpp └── toys ├── premake4.lua ├── sg.json └── src ├── chat.cpp ├── echo.cpp └── stability.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/test 3 | bin/kit 4 | toys/obj/ 5 | toys/bin/ 6 | *.make 7 | Makefile 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | 2 | [submodule "lib/local_shared_ptr"] 3 | path = lib/local_shared_ptr 4 | url = git@github.com:flipcoder/local_shared_ptr.git 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Grady O'Connell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kit 2 | Really cool C++ stuff, including modern async 3 | 4 | Open-source under MIT License 5 | 6 | Copyright (c) 2013 Grady O'Connell 7 | 8 | ## async 9 | - Coroutines w/ YIELD(), AWAIT(), and SLEEP() 10 | - Channels 11 | - Async Sockets 12 | - Event Multiplexer 13 | 14 | ```c++ 15 | // MX thread 0, void future 16 | MX[0].coro([]{ 17 | // do async stuff 18 | auto foo = AWAIT(bar); 19 | 20 | // async sleep yield 21 | SLEEP(chrono::milliseconds(100)); 22 | }); 23 | ``` 24 | 25 | ```c++ 26 | // socket example 27 | MX[0].coro([&]{ 28 | for(;;) 29 | { 30 | auto client = make_shared(AWAIT(server->accept())); 31 | 32 | // coroutine per client 33 | MX[0].coro([&, client]{ 34 | int client_id = client_ids++; 35 | LOGf("client %s connected", client_id); 36 | try{ 37 | for(;;) 38 | AWAIT(client->send(AWAIT(client->recv()))); 39 | }catch(const socket_exception& e){ 40 | LOGf("client %s disconnected (%s)", client_id % e.what()); 41 | } 42 | }); 43 | } 44 | }); 45 | 46 | ``` 47 | 48 | ## reactive 49 | signals, reactive values (signal-paired vars), and lazy evaluation 50 | 51 | ## meta 52 | JSON-compatible serializable meta-objects, property trees 53 | 54 | ## freq 55 | Timelines, alarms, animation/easing, waypoints/keyframes, interpolation 56 | 57 | ## log 58 | Logger w/ error handling, thread-safe scoped indent, silencing, and capturing 59 | 60 | ## math 61 | some math stuff to use with glm 62 | 63 | ## common (kit.h) 64 | Common stuff used by other modules, including: 65 | 66 | - freezable: freeze objects as immutable 67 | - make_unique: clone of c++14 function 68 | - dummy_mutex 69 | - ENTIRE() range macro 70 | - bit() and mask() 71 | - null_ptr_exception 72 | - scoped_unlock 73 | - thread-safe singleton 74 | - timed function auto-retry 75 | - index data structures w/ unused ID approximation 76 | 77 | -------------------------------------------------------------------------------- /bin/test.txt: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /kit.tasks: -------------------------------------------------------------------------------- 1 | * meta 2 | [ ] BUG: see "correct types" test case 3 | [?] some arrays still not being serialized/deserialized correctly 4 | [-] switch keys to bimap for faster removal key changes 5 | # may need to do some research first... (drop-in won't work) 6 | [|] json serialization (recursive) 7 | [ ] use pure flag for json hint (serialize as [] vs. {}?) 8 | [.] fast type check instead of branching -- use typeid as offset 9 | [.] containers (vectors, maps?) 10 | [-] faster index-in-parent look-up, possibly cached 11 | # update: don't actually need this anymore with weak-locking system 12 | # --- 13 | # ensure the parent pointer is respected in disconnect routines 14 | # incorporate m_IndexOfSelfInParent in add(), set(), etc.? 15 | [x] path 16 | # example: meta->path("this.is.a.path") 17 | [ ] path array indices 18 | # example: meta->path("items[3].subitems[1]") 19 | [ ] subdocument loading with a path 20 | # example: Meta("file.json:path.here") 21 | [-] binary 22 | [-] serial 23 | [-] deserial 24 | [|] Meta::Serializable 25 | [|] serialization 26 | [x] impl 27 | [ ] test 28 | [|] deserialization 29 | [x] impl 30 | [ ] test 31 | * iteration 32 | [-] move each() to .cpp file -- inl now 33 | [-] each_of_type<>() -- each() with check function isn't good enough? 34 | [-] any iteration -- each() is fine 35 | [L] input stream errors are vague and should include filename 36 | 37 | * args 38 | [x] Test cases for single-char switches 39 | [L] .meta() -- create metaobject based on args 40 | 41 | * kit.h (util) 42 | 43 | * async 44 | [ ] event lib + wakeup 45 | [x] stabilization of "idle" tasks (no CPU revving) 46 | [x] change sleep() -> CondVar.wait_for() 47 | # because: stabilization should't increase latency of non-idle tasks on same circuit 48 | [ ] allow (optional) additional circuits 49 | # socket-specific (for select() call optimization) 50 | # usage: disk-specific, blocking checks (low latency) 51 | # adding circuits will create contention on ThreadID->Circuit map, 52 | # so this is for long-running bg threads 53 | # or maybe: adding additional map for more circuits 54 | [-] fix helgrind-reported reporting race condition(s) 55 | # may be false positive ^ 56 | [-] task() and when() return scoped connections instead of future? 57 | 58 | * log 59 | [ ] scoped log props are unusable across coroutines 60 | # in order to fix we need Log coroutine_local data 61 | 62 | * freq 63 | [ ] Timelines and Alarms must be wrap-safe, if we're ever going to use this 64 | # for async's sleep mechanism 65 | [L] Longer scheduling (days, months) would be nice 66 | [L] Persistent timelines and alarms using serialization 67 | 68 | * reactive 69 | [ ] scoped slot removal 70 | 71 | -------------------------------------------------------------------------------- /kit/args/args.cpp: -------------------------------------------------------------------------------- 1 | #include "args.h" 2 | #include "../log/log.h" 3 | #include "../kit.h" 4 | #include 5 | using namespace std; 6 | 7 | void Args :: analyze() 8 | { 9 | // remove whitespace args 10 | kit::remove_if(m_Args, [](const string& s) { 11 | return (s.find_first_not_of(" \n\r\t") == string::npos); 12 | }); 13 | 14 | // parse key-values 15 | bool after_sep = false; 16 | for(const auto& arg: m_Args) 17 | { 18 | if(boost::starts_with(arg, "--")) 19 | { 20 | if(arg.length()==2) 21 | after_sep = true; // empty -- is a separator 22 | 23 | if(!after_sep) 24 | { 25 | size_t idx = arg.find('='); 26 | if(idx != string::npos) 27 | { 28 | if(idx != arg.rfind('=')) 29 | throw exception(); // more than one '=' 30 | string key = arg.substr(2, idx-2); 31 | if(m_Values.find(key) != m_Values.end()) 32 | throw exception(); // duplicate 33 | string value = arg.substr(idx+1); 34 | if(!value.empty()) 35 | if(!m_Values.insert({key,value}).second) 36 | throw exception(); // failed to insert 37 | 38 | m_Switches.insert(remove_dashes(key)); // insert key as switch 39 | } 40 | else 41 | { 42 | m_Switches.insert(remove_dashes(arg)); // just a switch, --test is test 43 | } 44 | } 45 | } 46 | else if(boost::starts_with(arg, "-")) // only one dash? (above check is two) 47 | { 48 | string flags = arg.substr(1); // remove one dash 49 | for(auto&& ch: flags) 50 | m_Switches.insert(string()+ch); // add chars separately 51 | } 52 | else 53 | { 54 | // no - or --, assume its a filename 55 | m_Filenames.push_back(arg); 56 | } 57 | } 58 | } 59 | 60 | string Args :: remove_dashes(string s, bool* success) // static 61 | { 62 | if(success) 63 | *success = false; 64 | 65 | if(boost::starts_with(s,"--")) 66 | { 67 | s = s.substr(2); 68 | if(success) 69 | *success = true; 70 | } 71 | else if(boost::starts_with(s,"-")) 72 | { 73 | s = s.substr(1); 74 | if(success) 75 | *success = true; 76 | } 77 | return s; 78 | } 79 | 80 | bool Args :: has(std::string op) const 81 | { 82 | bool removed; 83 | string flag = remove_dashes(op, &removed); 84 | if(not removed) 85 | return kit::has(m_Args, op); 86 | 87 | if(not flag.empty()) 88 | return kit::has(m_Switches, flag); 89 | 90 | assert(false); 91 | } 92 | 93 | bool Args :: has(char ch, std::string op) const 94 | { 95 | if(boost::starts_with(op, "--")) 96 | op = op.substr(2); // remove -- 97 | else if(boost::starts_with(op, "-")) 98 | op = op.substr(1); 99 | auto flag = string()+ch; 100 | return (kit::has(m_Switches, flag) || 101 | (not op.empty() && kit::has(m_Switches, op)) 102 | ); 103 | } 104 | 105 | void Args :: schema(std::string docstring) 106 | { 107 | if(docstring.empty()) 108 | { 109 | m_Schema = boost::optional(); 110 | return; 111 | } 112 | 113 | m_Schema = Schema(); 114 | 115 | vector lines; 116 | boost::split(lines, docstring, boost::is_any_of("\n")); 117 | for(auto&& line: lines) 118 | { 119 | boost::trim(line); 120 | 121 | // tokenize each switch as space-seperated values 122 | // until non-prefixed token (or EOL) 123 | if(boost::starts_with(line, "-")) 124 | { 125 | vector tokens; 126 | boost::split(tokens, line, boost::is_any_of(" \t")); 127 | for(auto&& tok: tokens) 128 | { 129 | if(boost::starts_with(tok, "--")) 130 | { 131 | m_Schema->allowed.push_back(tok); 132 | } 133 | else if(boost::starts_with(tok, "-")) 134 | { 135 | for(unsigned i=1;iallowed.push_back(string("-")+tok[i]); 137 | } 138 | else 139 | break; 140 | } 141 | } 142 | } 143 | } 144 | 145 | void Args :: validate() const 146 | { 147 | if(not m_Schema) 148 | return; 149 | 150 | for(auto&& arg: m_Args) 151 | { 152 | // compare against schema 153 | if(arg == "--") // sep 154 | break; // allow everything else 155 | else if(arg == "-") // ? 156 | { 157 | string fn = boost::filesystem::basename(m_Filename); 158 | K_ERRORf(GENERAL, 159 | "%s: unrecognized argument '%s'\nTry '%s --help for more information.'", 160 | fn % arg % fn 161 | ); 162 | } 163 | else if(boost::starts_with(arg, "--")) 164 | { 165 | if(not kit::has(m_Schema->allowed, arg)) 166 | { 167 | string fn = boost::filesystem::basename(m_Filename); 168 | K_ERRORf(GENERAL, 169 | "%s: unrecognized argument '%s'\nTry '%s --help for more information.'", 170 | fn % arg % fn 171 | ); 172 | } 173 | } 174 | else if(boost::starts_with(arg, "-")) 175 | { 176 | for(size_t i=0; iallowed, string("-")+letter)) 182 | { 183 | string fn = boost::filesystem::basename(m_Filename); 184 | K_ERRORf(GENERAL, 185 | "%s: unrecognized argument '-%s'\nTry '%s' --help for more information.", 186 | fn % letter % fn 187 | ); 188 | } 189 | } 190 | } 191 | } 192 | } 193 | 194 | -------------------------------------------------------------------------------- /kit/args/args.h: -------------------------------------------------------------------------------- 1 | #ifndef _ARGS_H_59ROLBDK 2 | #define _ARGS_H_59ROLBDK 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "../kit.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class Args 15 | //public kit::mutexed<> 16 | { 17 | public: 18 | Args() = default; 19 | Args(const Args&) = default; 20 | Args(Args&&) = default; 21 | Args& operator=(const Args&) = default; 22 | Args& operator=(Args&&) = default; 23 | 24 | Args(const std::vector& args, std::string docstring = ""): 25 | m_Args(args) 26 | { 27 | analyze(); 28 | schema(docstring); 29 | validate(); 30 | } 31 | 32 | Args(std::vector&& args, std::string docstring = ""): 33 | m_Args(args) 34 | { 35 | analyze(); 36 | schema(docstring); 37 | validate(); 38 | } 39 | 40 | Args(const std::string& lines, std::string docstring = ""): 41 | m_Args(kit::lines(lines)) 42 | { 43 | analyze(); 44 | schema(docstring); 45 | validate(); 46 | } 47 | 48 | Args(int argc, const char** argv, std::string docstring = ""): 49 | m_Args(argv+1, argv+argc), 50 | m_Filename(argv[0]) 51 | { 52 | analyze(); 53 | schema(docstring); 54 | validate(); 55 | } 56 | 57 | std::vector& get() { return m_Args; } 58 | const std::vector& get() const { return m_Args; }; 59 | 60 | size_t size() const { return m_Args.size(); } 61 | 62 | bool empty() const { return m_Args.empty(); } 63 | 64 | bool any(const std::vector& v) const { 65 | for(auto&& s: v) 66 | if(kit::has(m_Args, s)) 67 | return true; 68 | return false; 69 | } 70 | 71 | // [OLD] 72 | //bool has(const std::string& s) const { 73 | // return kit::has(m_Args, s); 74 | //} 75 | 76 | bool has_before( 77 | const std::string& match, const std::string& sep 78 | ) const { 79 | for(auto& s: m_Args){ 80 | if(s==sep) 81 | return false; 82 | if(s==match) 83 | return true; 84 | } 85 | return false; 86 | } 87 | bool has_after( 88 | const std::string& match, const std::string& sep 89 | ) const { 90 | for(auto& s: m_Args){ 91 | if(s==sep) 92 | continue; 93 | if(s==match) 94 | return true; 95 | } 96 | return false; 97 | 98 | } 99 | 100 | std::vector get_matches(std::string reg) const { 101 | boost::regex expr(reg); 102 | std::vector matches; 103 | for(const auto& s: m_Args) 104 | if(boost::regex_search(s, expr)) 105 | matches.push_back(s); 106 | return matches; 107 | } 108 | 109 | std::vector get_matches_after( 110 | std::string reg, 111 | std::string sep 112 | ) const { 113 | bool after = false; 114 | boost::regex expr(reg); 115 | std::vector matches; 116 | for(const auto& s: m_Args) { 117 | if(s==sep) { 118 | after=true; 119 | continue; 120 | } 121 | if(after) 122 | if(boost::regex_search(s, expr)) 123 | matches.push_back(s); 124 | } 125 | return matches; 126 | } 127 | 128 | std::vector get_matches_before( 129 | std::string reg, 130 | std::string sep 131 | ) const { 132 | //bool after = false; 133 | boost::regex expr(reg); 134 | std::vector matches; 135 | for(const auto& s: m_Args) { 136 | if(s==sep) 137 | return matches; 138 | //if(!after) 139 | if(boost::regex_search(s, expr)) 140 | matches.push_back(s); 141 | } 142 | return matches; 143 | } 144 | 145 | 146 | //std::vector> get_match_groups(boost::regex expr) const { 147 | // std::vector> matches; 148 | // boost::smatch results; 149 | // for(const auto& s: m_Args) 150 | // if(boost::regex_match(s, results, expr)) 151 | // matches.push_back(std::tuple(s, results)); 152 | // return matches; 153 | //} 154 | 155 | 156 | size_t num_matches(std::string reg) const { 157 | boost::regex expr(reg); 158 | size_t count = 0; 159 | for(const auto& s: m_Args) { 160 | if(boost::regex_search(s, expr)) 161 | ++count; 162 | } 163 | return count; 164 | } 165 | 166 | void set(const std::string& key, const std::string& value) { 167 | // TODO: check key and value formats (no spaces, etc.) 168 | m_Values[key] = value; 169 | m_Args.push_back((boost::format("--%s=%s") % key % value).str()); 170 | } 171 | //void value(const std::string& key, const std::string& value) { 172 | // // TODO: check key and value formats (no spaces, etc.) 173 | // m_Values[key] = value; 174 | // m_Args.push_back((boost::format("--%s=%s") % key % value).str()); 175 | //} 176 | std::string value(const std::string& key) const { 177 | try{ 178 | return m_Values.at(key); 179 | }catch(const std::out_of_range&){ 180 | return std::string(); 181 | } 182 | } 183 | std::string value_or( 184 | const std::string& key, 185 | const std::string& def = std::string() 186 | ) const { 187 | try{ 188 | return m_Values.at(key); 189 | }catch(const std::out_of_range&){ 190 | return def; 191 | } 192 | } 193 | 194 | std::string data() const { 195 | return boost::algorithm::join(m_Args, "\n"); 196 | } 197 | 198 | // TODO: tokenize based on arg separators 199 | // std::vector separate() const { 200 | // } 201 | 202 | //unsigned find(std::string reg) const { 203 | //} 204 | 205 | 206 | // TODO: I'm not liking the below functions at all, im going to use 207 | // a generator (coroutine-style) to do iterations throught this stuff 208 | // and add better functions later 209 | 210 | std::string get(unsigned pos) { 211 | try{ 212 | return m_Args.at(pos); 213 | }catch(const std::out_of_range&){ 214 | return std::string(); 215 | } 216 | } 217 | std::string after(unsigned pos, unsigned offset) { 218 | try{ 219 | return m_Args.at(pos + offset); 220 | }catch(const std::out_of_range&){ 221 | return std::string(); 222 | } 223 | } 224 | std::string before(unsigned pos, unsigned offset) { 225 | try{ 226 | if(offset>pos) 227 | return std::string(); 228 | return m_Args.at(pos - offset); 229 | }catch(const std::out_of_range&){ 230 | return std::string(); 231 | } 232 | } 233 | 234 | // [OLD] tests for a switch and its associated char 235 | //bool has(char c, std::string s) const { 236 | // if(boost::starts_with(s, "--")) 237 | // s = s.substr(2); // remove -- 238 | // else if(boost::starts_with(s, "-")) 239 | // s = s.substr(1); 240 | // if(has(c) || has(std::string("--")+s) 241 | // return true; 242 | // if(option(c,s)) 243 | // return true; 244 | // return false; 245 | //} 246 | 247 | // [DEPRECATED] 248 | //bool has(char c) const { 249 | // std::string chs = chars(); 250 | // return chs.find(c) != std::string::npos; 251 | //} 252 | 253 | // [DEPRECATED] returns string containing all provided char switches 254 | std::string chars() const { 255 | std::string r; 256 | for(auto&& a: m_Args) 257 | if(boost::starts_with(a, "-") && not boost::starts_with(a, "--")) 258 | r += a.substr(1); 259 | return r; 260 | } 261 | 262 | //std::string after(std::string s, unsigned offset) { 263 | //} 264 | //std::string before(std::string s, unsigned offset) { 265 | //} 266 | //std::string after_match(std::string s, unsigned offset) { 267 | //} 268 | //unsigned find(std::string s) {} 269 | 270 | std::string at(int idx, std::string def="") const { 271 | try{ 272 | if(idx >= 0) 273 | return m_Args.at(idx); 274 | return m_Args.at(m_Args.size() + idx); 275 | }catch(const std::out_of_range&){ 276 | return def; 277 | } 278 | } 279 | std::string filenames(int idx, std::string def="") const { 280 | try{ 281 | if(idx >= 0) 282 | return m_Filenames.at(idx); 283 | return m_Filenames.at(m_Filenames.size() + idx); 284 | }catch(const std::out_of_range&){ 285 | return def; 286 | } 287 | } 288 | 289 | typedef std::vector::iterator iterator; 290 | iterator begin() { return m_Args.begin(); } 291 | iterator end() { return m_Args.end(); } 292 | typedef std::vector::const_iterator const_iterator; 293 | const_iterator begin() const { return m_Args.begin(); } 294 | const_iterator end() const { return m_Args.end(); } 295 | const_iterator cbegin() const { return m_Args.begin(); } 296 | const_iterator cend() const { return m_Args.end(); } 297 | 298 | std::string filename() const { 299 | return m_Filename; 300 | } 301 | void filename(std::string fn) { 302 | m_Filename = fn; 303 | } 304 | 305 | bool has(std::string op) const; 306 | bool has(char ch, std::string full = std::string()) const; 307 | 308 | private: 309 | 310 | static std::string remove_dashes(std::string s, bool* success = nullptr); 311 | 312 | struct Schema 313 | { 314 | std::vector allowed; 315 | }; 316 | 317 | void analyze(); 318 | void schema(std::string docstring); 319 | void validate() const; 320 | 321 | std::vector m_Args; 322 | std::vector m_Filenames; 323 | //std::vector> m_Values; 324 | std::map m_Values; 325 | std::string m_Filename; 326 | boost::optional m_Schema; 327 | 328 | // all options broken down separately 329 | // ./program -abc --test 330 | // will contain -a, -b, -c, and --test 331 | // use has() to check this 332 | std::set m_Switches; 333 | }; 334 | 335 | #endif 336 | 337 | -------------------------------------------------------------------------------- /kit/async/async.h: -------------------------------------------------------------------------------- 1 | #ifndef ASYNC_H_TIBVELUE 2 | #define ASYNC_H_TIBVELUE 3 | 4 | #include "task.h" 5 | #include "mx.h" 6 | #include "channel.h" 7 | 8 | template 9 | class async_wrap 10 | { 11 | private: 12 | 13 | Multiplexer::Circuit* const m_pCircuit; 14 | T m_Data; 15 | 16 | public: 17 | 18 | async_wrap(Multiplexer::Circuit* circuit = &MX[0]): 19 | m_pCircuit(circuit), 20 | m_Data(T()) 21 | {} 22 | async_wrap(T&& v, Multiplexer::Circuit* circuit = &MX[0]): 23 | m_pCircuit(circuit), 24 | m_Data(v) 25 | {} 26 | async_wrap(const T& v, Multiplexer::Circuit* circuit = &MX[0]): 27 | m_pCircuit(circuit), 28 | m_Data(std::forward(v)) 29 | {} 30 | template 31 | std::future with(std::function cb) { 32 | return m_pCircuit->task([this, cb]{ 33 | return cb(m_Data); 34 | }); 35 | } 36 | template 37 | std::future with(std::function cb) const { 38 | return m_pCircuit->task([this, cb]{ 39 | return cb(m_Data); 40 | }); 41 | } 42 | 43 | // blocks until getting a copy of the wrapped m_Data 44 | T operator*() const { 45 | return get(); 46 | } 47 | T get() const { 48 | return m_pCircuit->task([this]{ 49 | return m_Data; 50 | }).get(); 51 | } 52 | 53 | const Multiplexer::Circuit& circuit() const { return *m_pCircuit; } 54 | }; 55 | 56 | #endif 57 | 58 | -------------------------------------------------------------------------------- /kit/async/async_fstream.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_H_VSWBGI51 2 | #define FILE_H_VSWBGI51 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "async.h" 9 | #include "../kit.h" 10 | 11 | class async_fstream 12 | { 13 | public: 14 | 15 | async_fstream(Multiplexer::Circuit* circuit = &MX[0]): 16 | m_pCircuit(circuit) 17 | {} 18 | //async_fstream( 19 | // std::string fn, Multiplexer::Circuit* circuit = &MX[0] 20 | //): 21 | // m_pCircuit(circuit), 22 | // m_Filename(fn) 23 | //{ 24 | // m_pCircuit->task([this, fn]{ 25 | // m_File.open(fn); 26 | // m_Filename = fn; 27 | // }); 28 | //} 29 | ~async_fstream() { 30 | close().get(); 31 | } 32 | 33 | async_fstream(const async_fstream&) = delete; 34 | async_fstream(async_fstream&&) = default; 35 | async_fstream& operator=(const async_fstream&) = default; 36 | async_fstream& operator=(async_fstream&&) = default; 37 | 38 | std::future open( 39 | std::ios_base::openmode mode = std::ios_base::in|std::ios_base::out 40 | ){ 41 | return m_pCircuit->task([this, mode]{ 42 | m_File.open(m_Filename, mode); 43 | }); 44 | } 45 | std::future open( 46 | std::string fn, 47 | std::ios_base::openmode mode = std::ios_base::in|std::ios_base::out 48 | ){ 49 | return m_pCircuit->task([this, fn, mode]{ 50 | _close(); 51 | m_File.open(fn, mode); 52 | m_Filename = fn; 53 | }); 54 | } 55 | 56 | std::future is_open() { 57 | return m_pCircuit->task([this]{ 58 | return m_File.is_open(); 59 | }); 60 | } 61 | std::future close() { 62 | return m_pCircuit->task([this]{ 63 | _close(); 64 | }); 65 | } 66 | std::future filename() const { 67 | return m_pCircuit->task( 68 | [this]{return m_Filename;} 69 | ); 70 | }; 71 | std::future buffer() const { 72 | return m_pCircuit->task([this]{ 73 | _cache(); 74 | return m_Buffer; 75 | }); 76 | } 77 | 78 | template 79 | std::future with(std::function func) { 80 | return m_pCircuit->task([this,func]{ return func(m_File); }); 81 | } 82 | template 83 | std::future with(std::function func) { 84 | return m_pCircuit->task([this,func]{ 85 | _cache(); 86 | return func(m_Buffer); 87 | }); 88 | } 89 | 90 | std::future invalidate() { 91 | return m_pCircuit->task( 92 | [this]{_invalidate();} 93 | ); 94 | } 95 | 96 | std::future recache() { 97 | return m_pCircuit->task( 98 | [this]{_invalidate();_cache();} 99 | ); 100 | } 101 | std::future cache() const { 102 | return m_pCircuit->task( 103 | [this]{_cache();} 104 | ); 105 | } 106 | 107 | private: 108 | 109 | void _close() { 110 | m_Filename = std::string(); 111 | _invalidate(); 112 | //if(m_File.is_open() 113 | m_File.close(); 114 | } 115 | 116 | void _invalidate() { 117 | m_Buffer.clear(); 118 | } 119 | void _cache() const { 120 | if(m_Buffer.empty()){ 121 | m_File.seekg(0, std::ios::end); 122 | m_Buffer.reserve(m_File.tellg()); 123 | m_File.seekg(0, std::ios::beg); 124 | m_Buffer.assign( 125 | (std::istreambuf_iterator(m_File)), 126 | std::istreambuf_iterator() 127 | ); 128 | } 129 | } 130 | 131 | Multiplexer::Circuit* const m_pCircuit; 132 | mutable std::fstream m_File; 133 | std::string m_Filename; 134 | 135 | mutable std::string m_Buffer; 136 | }; 137 | 138 | #endif 139 | 140 | -------------------------------------------------------------------------------- /kit/async/channel.h: -------------------------------------------------------------------------------- 1 | #ifndef CHANNEL_H_FC6YZN5J 2 | #define CHANNEL_H_FC6YZN5J 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "../kit.h" 14 | #include "task.h" 15 | 16 | template 17 | class Channel: 18 | public kit::mutexed 19 | //public std::enable_shared_from_this> 20 | { 21 | public: 22 | 23 | virtual ~Channel() {} 24 | 25 | // Put into stream 26 | void operator<<(T val) { 27 | auto l = this->lock(std::defer_lock); 28 | if(!l.try_lock()) 29 | throw kit::yield_exception(); 30 | if(m_bClosed) 31 | throw std::runtime_error("channel closed"); 32 | if(!m_Buffered || m_Vals.size() < m_Buffered) 33 | { 34 | m_Vals.push_back(std::move(val)); 35 | m_bNewData = true; 36 | return; 37 | } 38 | throw kit::yield_exception(); 39 | } 40 | template> 41 | void stream(Buffer& vals) { 42 | auto l = this->lock(std::defer_lock); 43 | if(!l.try_lock()) 44 | throw kit::yield_exception(); 45 | if(m_bClosed) 46 | throw std::runtime_error("channel closed"); 47 | size_t capacity = 0; 48 | size_t buflen = vals.size(); 49 | bool partial = false; 50 | if(m_Buffered) 51 | { 52 | capacity = m_Buffered - m_Vals.size(); 53 | if(buflen > capacity) 54 | { 55 | buflen = capacity; 56 | partial = true; 57 | } 58 | } 59 | if(buflen) 60 | { 61 | m_Vals.insert(m_Vals.end(), 62 | make_move_iterator(vals.begin()), 63 | make_move_iterator(vals.begin() + buflen) 64 | ); 65 | vals.erase(vals.begin(), vals.begin() + buflen); 66 | m_bNewData = true; 67 | if(partial) 68 | throw kit::yield_exception(); 69 | return; 70 | } 71 | throw kit::yield_exception(); 72 | } 73 | void operator<<(std::vector& vals) { 74 | stream(vals); 75 | } 76 | 77 | //void operator<<(std::vector val) { 78 | // auto l = this->lock(std::defer_lock); 79 | // if(!l.try_lock()) 80 | // throw kit::yield_exception(); 81 | // if(m_bClosed) 82 | // throw std::runtime_error("channel closed"); 83 | // if(!m_Buffered || m_Vals.size() < m_Buffered) { 84 | // m_Vals.push_back(std::move(val)); 85 | // m_bNewData = true; 86 | // return; 87 | // } 88 | // throw kit::yield_exception(); 89 | //} 90 | 91 | // Get from stream 92 | void operator>>(T& val) { 93 | if(not m_bNewData) 94 | throw kit::yield_exception(); 95 | auto l = this->lock(std::defer_lock); 96 | if(!l.try_lock()) 97 | throw kit::yield_exception(); 98 | //if(m_bClosed) 99 | // throw std::runtime_error("channel closed"); 100 | //m_bNewData = false; 101 | if(!m_Vals.empty()) 102 | { 103 | val = std::move(m_Vals.front()); 104 | m_Vals.pop_front(); 105 | if(m_Vals.empty()) 106 | m_bNewData = false; 107 | return; 108 | } 109 | throw kit::yield_exception(); 110 | } 111 | void operator>>(std::vector& vals) { 112 | get(vals); 113 | } 114 | template> 115 | void get(Buffer& vals) { 116 | if(not m_bNewData) 117 | throw kit::yield_exception(); 118 | auto l = this->lock(std::defer_lock); 119 | if(!l.try_lock()) 120 | throw kit::yield_exception(); 121 | //if(m_bClosed) 122 | // throw std::runtime_error("channel closed"); 123 | m_bNewData = false; 124 | if(!m_Vals.empty()) 125 | { 126 | vals.insert(vals.end(), 127 | make_move_iterator(m_Vals.begin()), 128 | make_move_iterator(m_Vals.end()) 129 | ); 130 | m_Vals.clear(); 131 | return; 132 | } 133 | throw kit::yield_exception(); 134 | } 135 | template> 136 | Buffer get() { 137 | Buffer buf; 138 | get(buf); 139 | return buf; 140 | } 141 | 142 | T peek() { 143 | if(not m_bNewData) 144 | throw kit::yield_exception(); 145 | auto l = this->lock(std::defer_lock); 146 | if(!l.try_lock()) 147 | throw kit::yield_exception(); 148 | //if(m_bClosed) 149 | // throw std::runtime_error("channel closed"); 150 | if(!m_Vals.empty()) 151 | return m_Vals.front(); 152 | throw kit::yield_exception(); 153 | } 154 | 155 | // NOTE: full buffers with no matching tokens will never 156 | // trigger this 157 | template> 158 | R get_until(T token) { 159 | if(not m_bNewData) 160 | throw kit::yield_exception(); 161 | auto l = this->lock(std::defer_lock); 162 | if(!l.try_lock()) 163 | throw kit::yield_exception(); 164 | //if(m_bClosed) 165 | // throw std::runtime_error("channel closed"); 166 | for(size_t i=0;ilock(std::defer_lock); 185 | if(!l.try_lock()) 186 | throw kit::yield_exception(); 187 | m_bNewData = 0; 188 | //if(m_bClosed) 189 | // throw std::runtime_error("channel closed"); 190 | if(!m_Vals.empty()) { 191 | auto r = std::move(m_Vals.front()); 192 | m_Vals.pop_front(); 193 | return r; 194 | } 195 | throw kit::yield_exception(); 196 | } 197 | 198 | //operator bool() const { 199 | // return m_bClosed; 200 | //} 201 | bool ready() const { 202 | return m_bNewData; 203 | } 204 | bool empty() const { 205 | auto l = this->lock(); 206 | return m_Vals.empty(); 207 | } 208 | size_t size() const { 209 | auto l = this->lock(); 210 | return m_Vals.size(); 211 | } 212 | size_t buffered() const { 213 | auto l = this->lock(); 214 | return m_Buffered; 215 | } 216 | void unbuffer() { 217 | auto l = this->lock(); 218 | m_Buffered = 0; 219 | } 220 | void buffer(size_t sz) { 221 | auto l = this->lock(); 222 | //if(sz > m_Buffered) 223 | // m_Vals.reserve(sz); 224 | m_Buffered = sz; 225 | } 226 | void close() { 227 | // atomic 228 | m_bClosed = true; 229 | } 230 | bool closed() const { 231 | // atomic 232 | return m_bClosed; 233 | } 234 | 235 | private: 236 | 237 | size_t m_Buffered = 0; 238 | std::deque m_Vals; 239 | std::atomic m_bClosed = ATOMIC_VAR_INIT(false); 240 | std::atomic m_bNewData = ATOMIC_VAR_INIT(false); 241 | }; 242 | 243 | #endif 244 | 245 | -------------------------------------------------------------------------------- /kit/async/mx.h: -------------------------------------------------------------------------------- 1 | #ifndef MULTIPLEXER_H_DY7UUXOY 2 | #define MULTIPLEXER_H_DY7UUXOY 3 | 4 | //#include "async.h" 5 | #include "../kit.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "../kit.h" 14 | #include "task.h" 15 | 16 | #define MX Multiplexer::get() 17 | 18 | #ifndef MX_THREADS 19 | #define MX_THREADS 0 20 | #endif 21 | 22 | #ifndef MX_FREQ 23 | #define MX_FREQ 0 24 | #endif 25 | 26 | #ifndef CACHE_LINE_SIZE 27 | #define CACHE_LINE_SIZE 64 28 | #endif 29 | 30 | typedef boost::coroutines::coroutine::pull_type pull_coro_t; 31 | typedef boost::coroutines::coroutine::push_type push_coro_t; 32 | 33 | // async await a future (continously yield until future is available or exception) 34 | #define AWAIT_MX(MUX, EXPR) \ 35 | [&]{\ 36 | while(true){\ 37 | try{\ 38 | return (EXPR);\ 39 | }catch(const kit::yield_exception&){\ 40 | MUX.yield();\ 41 | }\ 42 | }\ 43 | }() 44 | #define AWAIT(EXPR) AWAIT_MX(MX, EXPR) 45 | 46 | // async await a condition (continously yield until condition is true) 47 | #define YIELD_WHILE_MX(MUX, EXPR) \ 48 | [&]{\ 49 | while((EXPR)){\ 50 | MUX.yield();\ 51 | }\ 52 | }() 53 | #define YIELD_WHILE(EXPR) YIELD_WHILE_MX(MX, EXPR) 54 | #define YIELD_UNTIL_MX(MUX, EXPR) YIELD_WHILE_MX(MUX, not (EXPR)) 55 | #define YIELD_UNTIL(EXPR) YIELD_WHILE_MX(MX, not (EXPR)) 56 | 57 | #define YIELD_MX(MUX) MUX.yield(); 58 | #define YIELD() YIELD_MX(MX) 59 | 60 | #define AWAIT_HINT_MX(MUX, HINT, EXPR) \ 61 | [&]{\ 62 | bool once = false;\ 63 | while(true)\ 64 | {\ 65 | try{\ 66 | return (EXPR);\ 67 | }catch(const kit::yield_exception&){\ 68 | if(not once) {\ 69 | MUX.this_circuit().this_unit().cond = [=]{\ 70 | return (HINT);\ 71 | };\ 72 | once = true;\ 73 | }\ 74 | MUX.yield();\ 75 | }\ 76 | if(once)\ 77 | {\ 78 | kit::clear(MUX.this_circuit().this_unit().cond);\ 79 | }\ 80 | }\ 81 | }() 82 | 83 | #define AWAIT_HINT(HINT, EXPR) AWAIT_HINT_MX(MUX, HINT, EXPR) 84 | 85 | // coroutine async sleep() 86 | #define MX_SLEEP(TIME) Multiplexer::sleep(TIME); 87 | 88 | // deprecated 89 | #define SLEEP(TIME) Multiplexer::sleep(TIME); 90 | 91 | #define MX_EPSILON 0.00001 92 | 93 | class Multiplexer: 94 | public kit::singleton 95 | { 96 | public: 97 | 98 | struct Unit 99 | { 100 | Unit( 101 | std::function rdy, 102 | std::function func, 103 | std::unique_ptr&& push, 104 | pull_coro_t* pull 105 | ): 106 | m_Ready(rdy), 107 | m_Func(func), 108 | m_pPush(std::move(push)), 109 | m_pPull(pull) 110 | {} 111 | 112 | Unit( 113 | std::function rdy, 114 | std::function func 115 | ): 116 | m_Ready(rdy), 117 | m_Func(func) 118 | {} 119 | 120 | bool is_coroutine() const { 121 | return m_pPull; 122 | } 123 | 124 | // only a hint, assume ready if functor is 'empty' 125 | std::function m_Ready; 126 | Task m_Func; 127 | std::unique_ptr m_pPush; 128 | pull_coro_t* m_pPull = nullptr; 129 | // TODO: idletime hints for load balancing? 130 | }; 131 | 132 | class Circuit: 133 | //public IAsync, 134 | public kit::mutexed 135 | { 136 | public: 137 | 138 | Circuit(Multiplexer* mx, unsigned idx): 139 | m_pMultiplexer(mx), 140 | m_Index(idx) 141 | { 142 | run(); 143 | } 144 | virtual ~Circuit() {} 145 | 146 | void yield() { 147 | if(m_pCurrentUnit && m_pCurrentUnit->m_pPull) 148 | (*m_pCurrentUnit->m_pPull)(); 149 | else 150 | throw kit::yield_exception(); 151 | //else 152 | // boost::this_thread::yield(); 153 | } 154 | 155 | template 156 | std::future when(std::function cond, std::function cb) { 157 | while(true) { 158 | boost::this_thread::interruption_point(); 159 | { 160 | auto l = lock(); 161 | if(!m_Buffered || m_Units.size() < m_Buffered) { 162 | auto cbt = Task(std::move(cb)); 163 | auto fut = cbt.get_future(); 164 | auto cbc = boost::make_local_shared>(std::move(cbt)); 165 | m_Units.emplace_back(cond, [cbc]() { 166 | (*cbc)(); 167 | }); 168 | m_CondVar.notify_one(); 169 | return fut; 170 | } 171 | } 172 | boost::this_thread::yield(); 173 | } 174 | } 175 | 176 | template 177 | std::future coro(std::function cb) { 178 | while(true) { 179 | boost::this_thread::interruption_point(); 180 | { 181 | auto l = lock(); 182 | if(!m_Buffered || m_Units.size() < m_Buffered) { 183 | auto cbt = Task(std::move(cb)); 184 | auto fut = cbt.get_future(); 185 | auto cbc = boost::make_local_shared>(std::move(cbt)); 186 | m_Units.emplace_back( 187 | std::function(), 188 | std::function() 189 | ); 190 | auto* pullptr = &m_Units.back().m_pPull; 191 | m_Units.back().m_pPush = kit::make_unique( 192 | [cbc, pullptr](pull_coro_t& sink){ 193 | *pullptr = &sink; 194 | (*cbc)(); 195 | } 196 | ); 197 | auto* coroptr = m_Units.back().m_pPush.get(); 198 | m_Units.back().m_Ready = std::function( 199 | [coroptr]() -> bool { 200 | return bool(*coroptr); 201 | } 202 | ); 203 | m_Units.back().m_Func = Task(std::function( 204 | [coroptr]{ 205 | (*coroptr)(); 206 | if(*coroptr) // not completed? 207 | throw kit::yield_exception(); // continue 208 | } 209 | )); 210 | m_CondVar.notify_one(); 211 | return fut; 212 | } 213 | } 214 | boost::this_thread::yield(); 215 | } 216 | 217 | } 218 | 219 | template 220 | std::future task(std::function cb) { 221 | return when(std::function(), cb); 222 | } 223 | 224 | // TODO: handle single-direction channels that may block 225 | //template 226 | //std::shared_ptr> channel( 227 | // std::function>)> worker 228 | //) { 229 | // auto chan = boost::make_shared>(); 230 | // // ... inside lambda if(chan->closed()) remove? 231 | //} 232 | 233 | // TODO: handle multi-direction channels that may block 234 | 235 | template 236 | std::future when(std::future& fut, std::function&)> cb) { 237 | auto futc = boost::make_local_shared>(std::move(fut)); 238 | 239 | return task([cb, futc]() { 240 | if(futc->wait_for(std::chrono::seconds(0)) == 241 | std::future_status::ready) 242 | { 243 | cb(*futc); 244 | } 245 | else 246 | throw kit::yield_exception(); 247 | }); 248 | } 249 | 250 | void run() { 251 | m_Thread = boost::thread([this]{ 252 | m_pMultiplexer->m_ThreadToCircuit.with( 253 | [this](std::map& m){ 254 | m[boost::this_thread::get_id()] = m_Index; 255 | } 256 | ); 257 | unsigned idx = 0; 258 | try{ 259 | while(next(idx)){} 260 | }catch(const boost::thread_interrupted&){ 261 | m_Units.clear(); // this will unwind coros immediately 262 | } 263 | }); 264 | //#ifdef __WIN32__ 265 | // SetThreadPriority(m_Thread.native_handle(), THREAD_PRIORITY_BELOW_NORMAL); 266 | //#endif 267 | } 268 | //void forever() { 269 | // if(m_Thread.joinable()) { 270 | // m_Thread.join(); 271 | // } 272 | //} 273 | void finish_nojoin() { 274 | m_Finish = true; 275 | { 276 | auto l = this->lock>(); 277 | m_CondVar.notify_one(); 278 | } 279 | } 280 | void join() { 281 | if(m_Thread.joinable()) 282 | m_Thread.join(); 283 | } 284 | void finish() { 285 | m_Finish = true; 286 | { 287 | auto l = this->lock>(); 288 | m_CondVar.notify_one(); 289 | } 290 | if(m_Thread.joinable()) { 291 | m_Thread.join(); 292 | } 293 | } 294 | void stop() { 295 | if(m_Thread.joinable()) { 296 | m_Thread.interrupt(); 297 | { 298 | auto l = this->lock>(); 299 | m_CondVar.notify_one(); 300 | } 301 | m_Thread.join(); 302 | } 303 | } 304 | bool empty() const { 305 | auto l = this->lock>(); 306 | return m_Units.empty(); 307 | } 308 | void sync() { 309 | while(true){ 310 | if(empty()) 311 | return; 312 | boost::this_thread::yield(); 313 | }; 314 | } 315 | size_t buffered() const { 316 | auto l = lock(); 317 | return m_Buffered; 318 | } 319 | void unbuffer() { 320 | auto l = lock(); 321 | m_Buffered = 0; 322 | } 323 | void buffer(size_t sz) { 324 | auto l = lock(); 325 | m_Buffered = sz; 326 | } 327 | 328 | //virtual bool poll_once() override { assert(false); } 329 | //virtual void run() override { assert(false); } 330 | //virtual void run_once() override { assert(false); } 331 | 332 | Unit* this_unit() { return m_pCurrentUnit; } 333 | 334 | // expressed in maximum acceptable ticks per second when idle 335 | void frequency(float freq) { 336 | auto lck = this->lock>(); 337 | m_Frequency = freq; 338 | } 339 | 340 | private: 341 | 342 | void stabilize() 343 | { 344 | // WARNING: lock is assumed 345 | if(m_Frequency <= MX_EPSILON) 346 | return; // no stabilization 347 | const float inv_freq = 1.0f / m_Frequency; 348 | if(m_Clock != std::chrono::time_point()) 349 | { 350 | while(true) 351 | { 352 | auto now = std::chrono::system_clock::now(); 353 | auto elapsed = std::chrono::duration_cast 354 | (now - m_Clock).count() * 0.001f; 355 | if(elapsed < inv_freq) // in seconds 356 | { 357 | auto lck = this->lock>(boost::defer_lock); 358 | if(not lck.try_lock()) 359 | break; 360 | m_CondVar.timed_wait(lck, boost::posix_time::milliseconds( 361 | int((inv_freq - elapsed)*1000.0f) 362 | )); 363 | } 364 | else 365 | break; 366 | } 367 | } 368 | m_Clock = std::chrono::system_clock::now(); 369 | } 370 | 371 | // returns false only on empty() && m_Finish 372 | virtual bool next(unsigned& idx) { 373 | auto lck = this->lock>(); 374 | //if(l.try_lock()) 375 | // wait until task queued or thread interrupt 376 | while(true){ 377 | boost::this_thread::interruption_point(); 378 | if(not m_Units.empty()) 379 | break; 380 | else if(m_Finish) // finished AND empty 381 | return false; 382 | m_CondVar.wait(lck); 383 | boost::this_thread::yield(); 384 | continue; 385 | } 386 | 387 | if(idx >= m_Units.size()) { 388 | stabilize(); 389 | idx = 0; 390 | } 391 | 392 | auto& task = m_Units[idx]; 393 | if(!task.m_Ready || task.m_Ready()) { 394 | lck.unlock(); 395 | m_pCurrentUnit = &m_Units[idx]; 396 | try{ 397 | task.m_Func(); 398 | }catch(const kit::yield_exception&){ 399 | lck.lock(); 400 | const size_t sz = m_Units.size(); 401 | idx = std::min(idx+1, sz); 402 | if(idx == sz) { 403 | stabilize(); 404 | idx = 0; 405 | } 406 | return true; 407 | } 408 | m_pCurrentUnit = nullptr; 409 | lck.lock(); 410 | m_Units.erase(m_Units.begin() + idx); 411 | } 412 | return true; 413 | } 414 | 415 | Unit* m_pCurrentUnit = nullptr; 416 | std::deque m_Units; 417 | boost::thread m_Thread; 418 | size_t m_Buffered = 0; 419 | std::atomic m_Finish = ATOMIC_VAR_INIT(false); 420 | Multiplexer* m_pMultiplexer; 421 | unsigned m_Index=0; 422 | boost::condition_variable m_CondVar; 423 | std::chrono::time_point m_Clock; 424 | float m_Frequency = 1.0f * MX_FREQ; 425 | }; 426 | 427 | friend class Circuit; 428 | 429 | Multiplexer(bool init_now=true): 430 | m_Concurrency(std::max(1U, 431 | MX_THREADS ? MX_THREADS : 432 | boost::thread::hardware_concurrency() 433 | )) 434 | { 435 | if(init_now) 436 | init(); 437 | } 438 | virtual ~Multiplexer() { 439 | finish(); 440 | } 441 | void init() { 442 | for(unsigned i=0;i(this, i), CacheLinePadding() 445 | )); 446 | //m_MultiCircuit = kit::make_unique(this, i); 447 | } 448 | //void join() { 449 | // for(auto& s: m_Circuits) 450 | // s.join(); 451 | //} 452 | void stop() { 453 | for(auto& s: m_Circuits) 454 | std::get<0>(s)->stop(); 455 | } 456 | 457 | Circuit& any_circuit(){ 458 | // TODO: load balancing would be nice here 459 | return *std::get<0>((m_Circuits[std::rand() % m_Concurrency])); 460 | } 461 | 462 | Circuit& circuit(unsigned idx) { 463 | return *std::get<0>((m_Circuits[idx % m_Concurrency])); 464 | } 465 | Circuit& operator[](unsigned idx) { 466 | return *std::get<0>((m_Circuits[idx % m_Concurrency])); 467 | } 468 | 469 | void yield(){ 470 | try{ 471 | this_circuit().yield(); 472 | }catch(const std::out_of_range&){ 473 | throw kit::yield_exception(); 474 | } 475 | } 476 | 477 | static void sleep(std::chrono::milliseconds ms) { 478 | if(ms == std::chrono::milliseconds(0)) 479 | return; 480 | auto t0 = std::chrono::steady_clock::now(); 481 | while(true) { 482 | auto t1 = std::chrono::steady_clock::now(); 483 | if(std::chrono::duration_cast( 484 | t1 - t0 485 | ) >= ms) { 486 | break; 487 | } 488 | YIELD(); 489 | } 490 | } 491 | 492 | Circuit& this_circuit(){ 493 | return *m_ThreadToCircuit.with( 494 | [this](std::map& m 495 | ){ 496 | return &circuit(m.at(boost::this_thread::get_id())); 497 | }); 498 | } 499 | 500 | size_t size() const { 501 | return m_Concurrency; 502 | } 503 | 504 | void finish() { 505 | for(auto& s: m_Circuits) 506 | std::get<0>(s)->finish_nojoin(); 507 | for(auto& s: m_Circuits) 508 | std::get<0>(s)->join(); 509 | //for(auto& s: m_MultiCircuit) 510 | // std::get<0>(s)->finish_nojoin(); 511 | //for(auto& s: m_MultiCircuit) 512 | // std::get<0>(s)->join(); 513 | } 514 | 515 | //template 516 | //static bool retry(int count, Time delay, std::function func) 517 | //{ 518 | // for(int i=0; i, CacheLinePadding>> m_Circuits; 536 | //std::unique_ptr m_MultiCircuit; 537 | 538 | // read-write mutex might be more optimal here 539 | kit::mutex_wrap> m_ThreadToCircuit; 540 | }; 541 | 542 | #endif 543 | 544 | -------------------------------------------------------------------------------- /kit/async/task.h: -------------------------------------------------------------------------------- 1 | #ifndef TASK_H_HAPEX5Z1 2 | #define TASK_H_HAPEX5Z1 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "../kit.h" 9 | 10 | template 11 | class Task; 12 | 13 | template 14 | class Task 15 | { 16 | public: 17 | 18 | template 19 | explicit Task(T&&... t): 20 | m_Func(std::forward(t)...) 21 | {} 22 | 23 | Task(Task&&) = default; 24 | Task& operator=(Task&&) = default; 25 | 26 | Task(const Task&) = delete; 27 | Task& operator=(const Task&) = delete; 28 | 29 | template 30 | void operator()(T&&... t) { 31 | try{ 32 | m_Promise.set_value(m_Func(std::forward(t)...)); 33 | }catch(const kit::yield_exception& e){ 34 | throw e; 35 | }catch(...){ 36 | m_Promise.set_exception(std::current_exception()); 37 | } 38 | //}catch(...){ 39 | // assert(false); 40 | //} 41 | } 42 | 43 | std::future get_future() { 44 | return m_Promise.get_future(); 45 | } 46 | 47 | private: 48 | 49 | std::function m_Func; 50 | std::promise m_Promise; 51 | }; 52 | 53 | template 54 | class Task 55 | { 56 | public: 57 | 58 | template 59 | explicit Task(T&&... t): 60 | m_Func(std::forward(t)...) 61 | {} 62 | 63 | Task(Task&&) = default; 64 | Task& operator=(Task&&) = default; 65 | 66 | Task(const Task&) = delete; 67 | Task& operator=(const Task&) = delete; 68 | 69 | template 70 | void operator()(T&&... t) { 71 | try{ 72 | m_Func(std::forward(t)...); 73 | m_Promise.set_value(); 74 | //}catch(const boost::coroutines::detail::forced_unwind& e){ 75 | // throw e; 76 | }catch(const kit::yield_exception& e){ 77 | throw e; 78 | }catch(...){ 79 | m_Promise.set_exception(std::current_exception()); 80 | } 81 | } 82 | 83 | std::future get_future() { 84 | return m_Promise.get_future(); 85 | } 86 | 87 | private: 88 | 89 | std::function m_Func; 90 | std::promise m_Promise; 91 | 92 | }; 93 | 94 | #endif 95 | 96 | -------------------------------------------------------------------------------- /kit/cache/cache.h: -------------------------------------------------------------------------------- 1 | #ifndef _CACHE_H_QB0MZK5J 2 | #define _CACHE_H_QB0MZK5J 3 | 4 | #include 5 | #include "../factory/factory.h" 6 | #include "../log/log.h" 7 | #include "../kit.h" 8 | #include "icache.h" 9 | 10 | template< 11 | class Class, 12 | class T, 13 | template class Ptr=std::shared_ptr, 14 | class TMeta=Meta, 15 | class Mutex=kit::dummy_mutex 16 | > 17 | class Cache: 18 | public Factory, std::string, Ptr, TMeta, Mutex>, 19 | public ICache, 20 | virtual public kit::mutexed 21 | { 22 | public: 23 | //using factory_type = Factory, Ptr, std::string, Mutex>; 24 | 25 | Cache() = default; 26 | explicit Cache(std::string fn) 27 | //Factory<>(fn) 28 | { 29 | this->config(fn); 30 | } 31 | explicit Cache(typename TMeta::ptr cfg) 32 | //Factory<>(cfg) 33 | { 34 | this->config(cfg); 35 | } 36 | 37 | virtual ~Cache() {} 38 | 39 | /* 40 | * Optimize cache, keeping only resources in usage 41 | */ 42 | virtual void optimize() override { 43 | auto l = this->lock(); 44 | for(auto itr = m_Resources.begin(); 45 | itr != m_Resources.end();) 46 | { 47 | if(itr->second.unique()) 48 | itr = m_Resources.erase(itr); 49 | else 50 | ++itr; 51 | } 52 | } 53 | 54 | /* 55 | * Cache resource, force type 56 | */ 57 | template 58 | Ptr cache_as(T arg) { 59 | auto l = this->lock(); 60 | arg = transform(arg); 61 | if(m_Preserve && not m_Preserve(arg)) { 62 | return kit::make>( 63 | std::tuple(arg, this) 64 | ); 65 | } 66 | try{ 67 | return std::dynamic_pointer_cast(m_Resources.at(arg)); 68 | }catch(...){ 69 | auto p = kit::make>( 70 | //arg,this 71 | std::tuple(arg, this) 72 | ); 73 | m_Resources[arg] = std::static_pointer_cast(p); 74 | return p; 75 | } 76 | } 77 | 78 | /* 79 | * Cache resource, bypass parameter transformer 80 | */ 81 | virtual Ptr cache_raw(const T& arg) { 82 | auto l = this->lock(); 83 | if(m_Preserve && not m_Preserve(arg)) { 84 | return this->create( 85 | std::tuple(arg, this) 86 | ); 87 | } 88 | try{ 89 | return m_Resources.at(arg); 90 | }catch(const std::out_of_range&){ 91 | return (m_Resources[arg] = 92 | this->create( 93 | std::tuple(arg, this) 94 | ) 95 | ); 96 | } 97 | } 98 | 99 | /* 100 | * Cache resource and let factory choose type 101 | */ 102 | virtual Ptr cache(T arg) { 103 | auto l = this->lock(); 104 | arg = transform(arg); 105 | return cache_raw(arg); 106 | } 107 | 108 | //template 109 | //virtual void cache(const Ptr& blah) { 110 | //} 111 | 112 | /* 113 | * Cache resource and attempt to cast resource to a given type 114 | */ 115 | template 116 | Ptr cache_cast(T arg) { 117 | auto l = this->lock(); 118 | arg = transform(arg); 119 | Ptr p; 120 | //try{ 121 | p = std::dynamic_pointer_cast(cache_raw(arg)); 122 | if(!p) 123 | throw std::out_of_range("resource cast failed"); 124 | //}catch(...){ 125 | // typename std::unordered_map>::iterator itr; 126 | // try{ 127 | // itr = m_Resources.find(arg); 128 | // }catch(const std::out_of_range&){ 129 | // throw; 130 | // } 131 | // if(itr == m_Resources.end()) 132 | // throw std::out_of_range("no such resource"); 133 | // //if(itr->second.unique()) 134 | // // m_Resources.erase(itr); 135 | // throw; 136 | //} 137 | return p; 138 | } 139 | 140 | virtual size_t size() const override { 141 | auto l = this->lock(); 142 | return m_Resources.size(); 143 | } 144 | virtual bool empty() const override { 145 | auto l = this->lock(); 146 | return m_Resources.empty(); 147 | } 148 | 149 | /* 150 | * Completely clear ownership of ALL resources in cache. 151 | * Compare to optimize(), which clears only unused resources. 152 | */ 153 | virtual void clear() override { 154 | auto l = this->lock(); 155 | m_Resources.clear(); 156 | } 157 | 158 | /* 159 | * Allows transformer, a user-provided functor, to transform parameters 160 | * to ones usable by the underlying factory. 161 | */ 162 | T transform(const T& t){ 163 | auto l = this->lock(); 164 | if(m_Transformer) 165 | return m_Transformer(t); 166 | else 167 | return t; 168 | } 169 | /* 170 | * Registers the given functor as the transformer, a functor that can 171 | * transform parameters to ones usable by the underlying factory. 172 | */ 173 | void register_transformer(std::function f) { 174 | auto l = this->lock(); 175 | m_Transformer=f; 176 | } 177 | 178 | /* 179 | * Registers a preservation test, a functor that decides whether or not 180 | * to cache the resource, given the object's parameters. 181 | */ 182 | void register_preserver(std::function f) { 183 | auto l = this->lock(); 184 | m_Preserve = f; 185 | } 186 | 187 | private: 188 | 189 | std::unordered_map> m_Resources; 190 | std::function m_Transformer; 191 | std::function m_Preserve; 192 | }; 193 | 194 | #endif 195 | 196 | -------------------------------------------------------------------------------- /kit/cache/icache.h: -------------------------------------------------------------------------------- 1 | #ifndef _ICACHE_H 2 | #define _ICACHE_H 3 | 4 | //template 5 | class ICache 6 | { 7 | public: 8 | virtual ~ICache() {} 9 | //virtual std::shared_ptr cache(const T& arg) = 0; 10 | virtual void optimize() = 0; 11 | virtual void clear() = 0; 12 | virtual bool empty() const = 0; 13 | virtual size_t size() const = 0; 14 | }; 15 | 16 | #endif 17 | 18 | -------------------------------------------------------------------------------- /kit/factory/factory.h: -------------------------------------------------------------------------------- 1 | #ifndef _FACTORY_H_5UUKWBMC 2 | #define _FACTORY_H_5UUKWBMC 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | //#include 11 | #include 12 | #include 13 | #include "ifactory.h" 14 | #include "../kit.h" 15 | #include "../meta/meta.h" 16 | 17 | // TODO: add some way to have resolvers pass data into constructor instead 18 | // of relying on unchangable ctor parameters (hmm...) 19 | // TODO: or how about add second callback for after the ctor (?) 20 | // Let's use signals :D 21 | 22 | template< 23 | class Class, 24 | class T, 25 | class ClassName=std::string, 26 | template class Ptr=std::shared_ptr, 27 | class TMeta=Meta, // use MetaMT for config thread safety 28 | class Mutex=kit::dummy_mutex // or std::recursive_mutex 29 | > 30 | class Factory: 31 | public IFactory, 32 | virtual public kit::mutexed 33 | { 34 | private: 35 | 36 | // derived class make_shared() functors 37 | std::vector< 38 | std::function< 39 | Ptr(const T&) 40 | > 41 | > m_Classes; 42 | 43 | std::unordered_map m_ClassNames; 44 | 45 | //boost::signals2::signal m_Callback; 46 | 47 | // TODO: add this feature 48 | //std::map< 49 | // std::string name, 50 | // size_t index 51 | //> m_ClassNames; 52 | 53 | std::function m_Resolver; 54 | std::function m_Transformer; 55 | typename TMeta::ptr m_pConfig; 56 | 57 | public: 58 | 59 | Factory(): 60 | m_pConfig(kit::make()) 61 | {} 62 | explicit Factory(std::string fn): 63 | m_pConfig(kit::make(fn)) 64 | {} 65 | explicit Factory(typename TMeta::ptr cfg): 66 | m_pConfig(kit::make(cfg)) 67 | {} 68 | 69 | typename TMeta::ptr config() { 70 | auto l = this->lock(); 71 | return m_pConfig; 72 | } 73 | typename TMeta::cptr config() const { 74 | auto l = this->lock(); 75 | return m_pConfig; 76 | } 77 | void config(typename TMeta::ptr cfg) {m_pConfig=cfg;} 78 | void config(std::string cfg) {m_pConfig=kit::make(cfg);} 79 | //void with(std::function cb){ 80 | // auto l = this->lock(); 81 | // cb(); 82 | //} 83 | 84 | virtual ~Factory() {} 85 | 86 | bool empty() const { 87 | auto l = this->lock(); 88 | return m_Classes.empty(); 89 | } 90 | 91 | //TOD: register_class should allow ID to set to instead of returning one? 92 | template 93 | unsigned register_class( 94 | ClassName name = ClassName(), 95 | unsigned id = std::numeric_limits::max() 96 | ) { 97 | // bind subclass's make_shared() to functor and store it in m_Classes list 98 | // TODO: exceptions? 99 | auto l = this->lock(); 100 | 101 | const size_t size = m_Classes.size(); 102 | if(id == std::numeric_limits::max()) // don't care about class ID 103 | { 104 | m_Classes.push_back( 105 | std::bind( 106 | &std::make_shared, 107 | std::placeholders::_1 108 | ) 109 | ); 110 | if(!name.empty()) 111 | m_ClassNames[name] = static_cast(size); 112 | return static_cast(size); 113 | } 114 | else 115 | { 116 | if(id+1 > size) // not max ID 117 | m_Classes.resize(id+1); 118 | m_Classes[id] = std::bind( 119 | &std::make_shared, 120 | std::placeholders::_1 121 | ); 122 | if(!name.empty()) 123 | m_ClassNames[name] = static_cast(size); 124 | return id; 125 | } 126 | } 127 | 128 | void register_resolver(std::function func) { 129 | auto l = this->lock(); 130 | m_Resolver = func; 131 | } 132 | 133 | unsigned class_id(ClassName name) const { 134 | auto l = this->lock(); 135 | try{ 136 | return m_ClassNames.at(name); 137 | } catch(const std::out_of_range&) {} 138 | return std::numeric_limits::max(); 139 | } 140 | static unsigned invalid_id() { 141 | return std::numeric_limits::max(); 142 | } 143 | 144 | std::shared_ptr create(unsigned id, T args) const { 145 | auto l = this->lock(); 146 | if(m_Transformer) 147 | args = m_Transformer(args); 148 | return m_Classes.at(id)(args); 149 | } 150 | 151 | std::shared_ptr create(T args) const { 152 | auto l = this->lock(); 153 | if(m_Transformer) 154 | args = m_Transformer(args); 155 | unsigned id = m_Resolver(args); 156 | return create(id, args); 157 | } 158 | 159 | template 160 | std::shared_ptr create_as(const T& args) const { 161 | return std::dynamic_pointer_cast(create(args)); 162 | } 163 | template 164 | std::shared_ptr create_as(unsigned id, const T& args) const { 165 | return std::dynamic_pointer_cast(create(id, args)); 166 | } 167 | 168 | void register_transformer(std::function f) { 169 | auto l = this->lock(); 170 | m_Transformer=f; 171 | } 172 | 173 | T transform(const T& t){ 174 | return m_Transformer(t); 175 | } 176 | 177 | //void share_config(TMeta::Ptr cfg){ 178 | // //auto l = this->lock(); 179 | // m_pConfig = cfg; 180 | //} 181 | //TMeta::Ptr share_config() { 182 | // //auto l = this->lock(); 183 | // return m_pConfig; 184 | //} 185 | 186 | //std::vector> create_all( 187 | // const std::vector& object_list 188 | //) const { 189 | // std::vector> objects; 190 | // objects.reserve(object_list.size()); 191 | // for(const auto& params: object_list) { 192 | // objects.push_back(create(params)); 193 | // } 194 | // return objects; 195 | //} 196 | }; 197 | 198 | //template std::vector< 199 | // std::function< 200 | // std::shared_ptr(boost::any&) 201 | // > 202 | //> Factory ::m_Classes; 203 | 204 | //template std::function< 205 | // unsigned(boost::any&) 206 | //> Factory ::m_Resolver; 207 | 208 | #endif 209 | 210 | -------------------------------------------------------------------------------- /kit/factory/ifactory.h: -------------------------------------------------------------------------------- 1 | #ifndef _IFACTORY_H 2 | #define _IFACTORY_H 3 | 4 | class IFactory 5 | { 6 | public: 7 | virtual ~IFactory() {} 8 | }; 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /kit/freq/animation.h: -------------------------------------------------------------------------------- 1 | #ifndef _ANIMATION_H 2 | #define _ANIMATION_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../kit.h" 11 | #include "freq.h" 12 | #include "../math/common.h" 13 | 14 | template class Animation; 15 | 16 | namespace Interpolation { 17 | template 18 | T linear(T a, T b, float t) { 19 | return a + (b-a)*t; 20 | } 21 | template 22 | T out_sine(T a, T b, float t) { 23 | const float qtau = K_TAU / 4.0f; 24 | const float nt = sin(t * qtau); 25 | return linear(a,b,nt); 26 | } 27 | template 28 | T in_sine(T a, T b, float t) { 29 | const float qtau = K_TAU / 4.0f; 30 | const float nt = 1.0f + sin((t - 1.0f) * qtau); 31 | return linear(a,b,nt); 32 | } 33 | //(1-sqrt(x))|cos(x*2*tau)| 34 | template 35 | T bounce(T a, T b, float t) { 36 | const float nt = 1.0f - (std::pow(1.0f-t, depth)) * 37 | std::fabs(std::cos(t * bounces * K_PI)); 38 | return linear(a,b,nt); 39 | } 40 | //f(x) = sin(2pi*(.75x-0.25))+1.0 41 | template 42 | T exaggerate(T a, T b, float t) { 43 | float nt = std::sin(K_TAU*(0.75*t + 0.25)) + 1.0f; 44 | return linear(a,b,nt); 45 | } 46 | } 47 | 48 | #define INTERPOLATE(FUNC)\ 49 | std::bind(\ 50 | &Interpolation::FUNC,\ 51 | std::placeholders::_1,\ 52 | std::placeholders::_2,\ 53 | std::placeholders::_3\ 54 | )\ 55 | 56 | /* 57 | * Template class should be a floating point value, a (math) vector, 58 | * matrix, or quaternion. But it can also be a color, or anything 59 | * interpolatable. 60 | * 61 | * You define the easing function in the form of: 62 | * T func(const T&, const T&, float t) 63 | * Where t is a value that ranges from 0 to 1. 64 | * 65 | * Time is in seconds, but exists on a "timeline", so the actual real time 66 | * value can be whatever you want, depending on how fast you advance the 67 | * timeline. 68 | * 69 | * Negative time values are not yet supported. 70 | * I don't have derivative approximation yet, but I think it could be useful 71 | * for nonlinear easing functions when you need to also know the speed. 72 | */ 73 | template 74 | class Frame 75 | //public IRealtime 76 | { 77 | private: 78 | 79 | // TODO: execution callback? 80 | // TODO: expiry callback? 81 | // TODO: interpolative callback? 82 | T m_Value; 83 | 84 | Freq::Time m_Time; // length of time 85 | Freq::Alarm m_Alarm; 86 | 87 | std::function m_Easing; 88 | 89 | //Animation* m_pAnimation; 90 | Freq::Timeline* m_pTimeline; 91 | 92 | std::shared_ptr> m_pCallback; 93 | 94 | public: 95 | 96 | Frame( 97 | T value, 98 | Freq::Time time = Freq::Time(0), 99 | std::function easing = 100 | std::function(), 101 | std::function cb = std::function(), 102 | Freq::Timeline* timeline = nullptr 103 | ): 104 | m_Value(value), 105 | m_Time(time), 106 | m_Easing(easing), 107 | m_pTimeline(timeline), 108 | m_Alarm(timeline), 109 | m_pCallback(std::make_shared>()) 110 | //m_pAnimation(nav) 111 | { 112 | if(cb) 113 | m_pCallback->connect(std::move(cb)); 114 | //assert(m_pTimeline); 115 | } 116 | //void nav(Animation* nav) { 117 | // assert(nav); 118 | // m_pAnimation = nav; 119 | //} 120 | 121 | virtual ~Frame() {} 122 | void reset() { 123 | m_Alarm.set(m_Time); 124 | } 125 | 126 | template 127 | void connect(Func func) { 128 | m_pCallback->connect(func); 129 | } 130 | 131 | std::shared_ptr> callback() { 132 | return m_pCallback; 133 | } 134 | void callback(std::function cb) { 135 | m_pCallback->connect(std::move(cb)); 136 | } 137 | 138 | //virtual void logic(Freq::Time t) { 139 | // //m_pTimeline->logic(t); 140 | // // TODO: trigger callbacks here? 141 | //} 142 | 143 | virtual bool elapsed() { 144 | return m_Alarm.elapsed(); 145 | } 146 | 147 | unsigned long remaining() { 148 | return m_Alarm.ms(); 149 | } 150 | 151 | Freq::Alarm& alarm() { return m_Alarm; } 152 | 153 | T& value() { 154 | return m_Value; 155 | } 156 | 157 | void easing(std::function func) { 158 | m_Easing = func; 159 | } 160 | 161 | const std::function& easing() const { 162 | return m_Easing; 163 | } 164 | 165 | void timeline(Freq::Timeline* tl) { 166 | m_pTimeline = tl; 167 | m_Alarm.timer(m_pTimeline); 168 | } 169 | Freq::Timeline* timeline() { return m_pTimeline; } 170 | }; 171 | 172 | 173 | class IAnimation 174 | { 175 | public: 176 | virtual ~IAnimation() {} 177 | }; 178 | 179 | template 180 | class Animation: 181 | public IAnimation 182 | { 183 | private: 184 | 185 | mutable std::deque> m_Frames; 186 | mutable T m_Current = T(); 187 | 188 | //float m_fSpeed = 1.0f; 189 | //Freq::Timeline* m_pTimeline = nullptr; 190 | Freq::Timeline m_Timeline; 191 | 192 | void reset(Freq::Time excess = Freq::Time(0)) const { 193 | if(m_Frames.empty()) 194 | return; 195 | 196 | m_Frames.front().reset(); 197 | m_Frames.front().alarm() += excess; 198 | } 199 | 200 | void process() const { 201 | while(!m_Frames.empty() && m_Frames.front().elapsed()) 202 | { 203 | auto& frame = m_Frames.front(); 204 | Freq::Time excess = frame.alarm().excess(); 205 | m_Current = frame.value(); 206 | std::shared_ptr> cb = 207 | frame.callback(); 208 | m_Frames.pop_front(); 209 | reset(excess); 210 | (*cb)(); 211 | } 212 | } 213 | 214 | public: 215 | 216 | Animation() = default; 217 | //Animation(T&& current): 218 | // m_Current(current) 219 | //{} 220 | Animation& operator=(T&& a){ 221 | m_Current = a; 222 | return *this; 223 | } 224 | virtual ~Animation() {} 225 | 226 | Frame* first_frame() { 227 | if(!m_Frames.empty()) 228 | return &m_Frames.front(); 229 | return nullptr; 230 | } 231 | Frame* last_frame() { 232 | if(!m_Frames.empty()) 233 | return &m_Frames.back(); 234 | return nullptr; 235 | } 236 | 237 | //Animation( 238 | // Frame initial, 239 | // T current=T() 240 | //): 241 | // m_Current(current) 242 | // //m_fSpeed(1.0f) 243 | //{ 244 | // //initial.nav(this); 245 | // initial.timeline(&m_Timeline); 246 | // m_Frames.push_back(initial); 247 | // reset(); 248 | //} 249 | 250 | size_t size() const { 251 | return m_Frames.size(); 252 | } 253 | 254 | void from( 255 | T current=T() 256 | ) { 257 | m_Current=current; 258 | } 259 | 260 | operator T() const { 261 | return get(); 262 | } 263 | 264 | virtual void logic(Freq::Time t) { 265 | m_Timeline.logic(t); 266 | } 267 | 268 | void pause() { 269 | m_Timeline.pause(); 270 | } 271 | void resume() { 272 | m_Timeline.resume(); 273 | } 274 | 275 | /* 276 | * Append a frame to the end of the cycle 277 | */ 278 | void frame(Frame frame) { 279 | frame.timeline(&m_Timeline); 280 | m_Frames.push_back(frame); 281 | reset(); 282 | } 283 | 284 | bool elapsed() const { 285 | process(); 286 | return m_Frames.empty(); 287 | } 288 | 289 | void abort(){ 290 | m_Current = get(); 291 | m_Frames.clear(); 292 | } 293 | 294 | void do_callbacks() 295 | { 296 | for(auto&& frame: m_Frames) 297 | { 298 | auto cb = frame.callback(); 299 | if(cb) (*cb)(); 300 | } 301 | } 302 | void finish(){ 303 | if(!m_Frames.empty()) { 304 | auto current = m_Frames.front().value(); 305 | process(); 306 | do_callbacks(); 307 | m_Frames.clear(); 308 | m_Current = current; 309 | } 310 | } 311 | void stop(T position = T()) { 312 | m_Current = position; 313 | m_Frames.clear(); 314 | } 315 | void stop( 316 | T position, 317 | Freq::Time t, 318 | std::function easing, 319 | std::function cb = std::function() 320 | ){ 321 | m_Current = t.ms() ? get() : position; 322 | m_Frames.clear(); 323 | 324 | if(t.ms()) 325 | { 326 | auto f = Frame(position, t, easing); 327 | f.timeline(&m_Timeline); 328 | if(cb) 329 | f.connect(cb); 330 | m_Frames.push_back(std::move(f)); 331 | reset(); 332 | } 333 | } 334 | 335 | void ensure( 336 | T position, 337 | Freq::Time t, 338 | std::function easing, 339 | std::function equality_cmp 340 | ){ 341 | process(); 342 | 343 | if(equality_cmp(m_Current, position)) 344 | { 345 | // TODO: if t < current frame time left 346 | // use t instead 347 | return; 348 | } 349 | 350 | if(m_Frames.empty()) { 351 | stop(position,t,easing); 352 | return; 353 | } 354 | } 355 | 356 | T get() const { 357 | process(); 358 | if(m_Frames.empty()) 359 | return m_Current; 360 | 361 | return ease( 362 | m_Frames.front().easing(), 363 | m_Current, 364 | m_Frames.front().value(), 365 | m_Frames.front().alarm().fraction() 366 | ); 367 | } 368 | 369 | T ease( 370 | const std::function& easing, 371 | T a, 372 | T b, 373 | float t 374 | ) const { 375 | return easing(a,b,t); 376 | } 377 | 378 | bool empty() const { 379 | return m_Frames.empty(); 380 | } 381 | 382 | //void speed(float s) { 383 | // m_fSpeed = s; 384 | //} 385 | //float speed() const { 386 | // return m_fSpeed; 387 | //} 388 | 389 | //void timeline(Freq::Timeline* tl) { 390 | // m_pTimeline = tl; 391 | //} 392 | Freq::Timeline* timeline() { return &m_Timeline; } 393 | }; 394 | 395 | #endif 396 | 397 | -------------------------------------------------------------------------------- /kit/freq/freq.h: -------------------------------------------------------------------------------- 1 | #ifndef _FREQ_H 2 | #define _FREQ_H 3 | 4 | #include 5 | #include 6 | #include "../kit_compat.h" 7 | #include "../math/common.h" 8 | #include "../kit.h" 9 | #include "../log/log.h" 10 | 11 | #ifndef K_GET_TICKS 12 | #include 13 | #define K_GET_TICKS SDL_GetTicks 14 | #endif 15 | 16 | class Freq 17 | { 18 | public: 19 | 20 | class Time 21 | { 22 | public: 23 | //unsigned int value; 24 | float sec; 25 | Time(): 26 | sec(0) {} 27 | explicit Time(float ms) { sec = ms/1000.0f; } // deprecated 28 | Time(const Time& t) = default; 29 | Time& operator=(const Time& t) = default; 30 | Time(Time&& t) = default; 31 | Time& operator=(Time&& t) = default; 32 | 33 | unsigned ui() const { return (unsigned int)(sec*1000.0f); } 34 | //static Time seconds(unsigned int s) { return Time(s * 1000);} 35 | static Time seconds(float s) { return Time(s*1000.0f); } 36 | //static Time minutes(unsigned int m) { return Time(m * 60000);} 37 | static Time minutes(float m) { return Time(m * 60000.0f);} 38 | static Time ms(float ms) { return Time(ms); } 39 | 40 | //operator bool() const { return sec; } 41 | //operator float() const { return sec / 1000.0f; } 42 | 43 | Time& operator+=(Time t) { 44 | sec += t.sec; 45 | return *this; 46 | } 47 | bool operator>(Time t) const { return sec > t.sec; } 48 | bool operator<(Time t) const { return sec < t.sec; } 49 | bool operator>=(Time t) const { return sec >= t.sec; } 50 | bool operator<=(Time t) const { return sec <= t.sec; } 51 | operator bool() const { 52 | return std::abs(sec*1000.0f) > K_EPSILON; 53 | } 54 | 55 | float s() const { return sec; } 56 | float seconds() const { return sec; } 57 | float ms() const { return sec * 1000.0f; } 58 | }; 59 | 60 | class Timeline { 61 | private: 62 | //unsigned long m_ulPassedTime; 63 | unsigned long m_ulPassedTime; 64 | //unsigned int m_uiLastAdvance; 65 | float m_fSpeed; 66 | public: 67 | Timeline(): 68 | m_ulPassedTime(0L), 69 | m_fSpeed(1.0f) 70 | { 71 | assert(m_ulPassedTime == 0L); 72 | assert(m_fSpeed == 1.0f); 73 | } 74 | virtual ~Timeline() {} 75 | 76 | Timeline(const Timeline&) = default; 77 | Timeline(Timeline&&) = default; 78 | Timeline& operator=(const Timeline&) = default; 79 | Timeline& operator=(Timeline&&) = default; 80 | 81 | virtual unsigned long ms() const { 82 | return m_ulPassedTime; 83 | } 84 | virtual float seconds() const { 85 | return m_ulPassedTime / 1000.0f; 86 | } 87 | 88 | /*virtual */Freq::Time logic(Freq::Time t) { // ms 89 | float advance = t.ms() * m_fSpeed; 90 | unsigned adv = kit::round_int(advance); 91 | //auto adv = std::rint(advance); 92 | //return Freq::Time::ms((unsigned long)(advance)); 93 | //LOGf("passed time: %s", m_ulPassedTime); 94 | //LOGf("passed time += : %s", t.ms()); 95 | m_ulPassedTime += adv; 96 | return Freq::Time::ms(adv); 97 | } 98 | //float logic(float a) { // seconds 99 | // float advance = a * m_fSpeed; 100 | // //m_uiLastAdvance = round_int(advance); 101 | // m_ulPassedTime += std::rint(advance * 1000.0f); 102 | // return advance; 103 | //} 104 | //unsigned int advance() const { return m_uiLastAdvance; } 105 | 106 | void speed(float s) { 107 | m_fSpeed = s; 108 | } 109 | float speed() const { 110 | return m_fSpeed; 111 | } 112 | void pause() { 113 | m_fSpeed = 0.0f; 114 | } 115 | void resume(float speed = 1.0f) { 116 | m_fSpeed = speed; 117 | } 118 | void reset() { 119 | m_ulPassedTime = 0L; 120 | m_fSpeed = 1.0f; 121 | } 122 | 123 | bool elapsed(Freq::Time value) { 124 | return m_ulPassedTime > value.ui(); 125 | } 126 | Freq::Time age() const { 127 | return Freq::Time::ms(m_ulPassedTime); 128 | } 129 | }; 130 | 131 | // eventually migrate to chrono 132 | class Alarm 133 | { 134 | protected: 135 | 136 | //Freq* m_pTimer; 137 | Timeline* m_pTimer; 138 | unsigned long m_ulAlarmTime; 139 | unsigned long m_ulStartTime; 140 | bool m_bStarted = false; 141 | 142 | std::shared_ptr> m_pCallback = 143 | std::make_shared>(); 144 | 145 | public: 146 | 147 | //Alarm(): 148 | // m_ulAlarmTime(0L), 149 | // m_ulStartTime(0L), 150 | // m_pTimer(Freq::get().accumulator()) 151 | //{ 152 | // assert(m_pTimer); 153 | //} 154 | 155 | Alarm() {} 156 | 157 | explicit Alarm(Timeline* timer): 158 | m_pTimer(timer), 159 | m_ulAlarmTime(0L), 160 | m_ulStartTime(0L) 161 | { 162 | //assert(m_pTimer); 163 | } 164 | 165 | Alarm(const Alarm&) = default; 166 | Alarm(Alarm&&) = default; 167 | Alarm& operator=(const Alarm&) = default; 168 | Alarm& operator=(Alarm&&) = default; 169 | 170 | explicit Alarm(Time t, Timeline* timer): 171 | m_pTimer(timer) 172 | //m_pTimer(timer ? timer : Freq::get().accumulator()) 173 | { 174 | //assert(m_pTimer); 175 | set(t); 176 | } 177 | explicit Alarm(Time t, Timeline* timer, std::function func): 178 | m_pTimer(timer), 179 | m_pCallback(std::make_shared>()) 180 | //m_pTimer(timer ? timer : Freq::get().accumulator()) 181 | { 182 | //assert(m_pTimer); 183 | set(t); 184 | m_pCallback->connect(func); 185 | } 186 | 187 | 188 | virtual ~Alarm() {} 189 | 190 | Alarm& operator+=(Freq::Time t) { 191 | m_ulAlarmTime += t.ms(); 192 | return *this; 193 | } 194 | 195 | void reset() { 196 | m_ulAlarmTime = 0L; 197 | m_ulStartTime = 0L; 198 | m_bStarted = false; 199 | } 200 | 201 | bool has_timer() const { return (m_pTimer!=NULL); } 202 | 203 | void timer(Timeline* timerRef) 204 | { 205 | assert(timerRef); 206 | m_pTimer = timerRef; 207 | } 208 | const Timeline* timer() const { return m_pTimer; } 209 | Timeline* timer() { return m_pTimer; } 210 | 211 | void set(Time t, Timeline* timer = NULL) 212 | { 213 | if(timer) 214 | m_pTimer = timer; 215 | assert(m_pTimer); 216 | m_ulStartTime = m_pTimer->ms(); 217 | m_ulAlarmTime = m_ulStartTime + t.ms(); 218 | m_bStarted = true; 219 | } 220 | 221 | void delay(Time t) { 222 | assert(m_pTimer); 223 | m_ulAlarmTime += ((unsigned long)t.ms()); 224 | } 225 | 226 | Freq::Time pause() { 227 | return Freq::Time::ms(m_ulAlarmTime - m_ulStartTime); 228 | } 229 | 230 | void minutes(unsigned int m) 231 | { 232 | set(Time::minutes(m)); 233 | } 234 | 235 | void seconds(unsigned int s) 236 | { 237 | set(Time::seconds(s)); 238 | } 239 | 240 | void ms(unsigned int ms) 241 | { 242 | set(Time(ms)); 243 | } 244 | 245 | unsigned long ms() const 246 | { 247 | assert(m_pTimer); 248 | if(!elapsed()) 249 | { 250 | unsigned long t = (m_ulAlarmTime - m_pTimer->ms()); 251 | return t; 252 | } 253 | return 0L; 254 | } 255 | 256 | float seconds() const 257 | { 258 | assert(m_pTimer); 259 | float t = (m_ulAlarmTime - m_pTimer->ms()) / 1000.0f; 260 | return t; 261 | } 262 | 263 | bool elapsed() const 264 | { 265 | if(not m_bStarted) 266 | return false; 267 | assert(m_pTimer); 268 | return (m_pTimer->ms() >= m_ulAlarmTime); 269 | } 270 | 271 | float fraction() const 272 | { 273 | return 1.0f - fraction_left(); 274 | } 275 | 276 | Freq::Time excess() const { 277 | if(!elapsed()) 278 | return Freq::Time(0); 279 | return Freq::Time::ms(m_pTimer->ms() - m_ulAlarmTime); 280 | } 281 | 282 | boost::signals2::connection connect(std::function cb) { 283 | return m_pCallback->connect(std::move(cb)); 284 | } 285 | bool poll() { 286 | if(elapsed()) { 287 | (*m_pCallback)(); 288 | m_pCallback->disconnect_all_slots(); 289 | return true; 290 | } 291 | return false; 292 | } 293 | 294 | float fraction_left() const 295 | { 296 | if(elapsed()) 297 | return 0.0f; 298 | 299 | unsigned long remaining = ms(); 300 | unsigned long range = m_ulAlarmTime - m_ulStartTime; 301 | if(floatcmp(range, 0.0f)) 302 | return 0.0f; 303 | return (float)remaining / (float)range; 304 | } 305 | 306 | bool started() const { 307 | return m_bStarted; 308 | } 309 | 310 | //unsigned long endTickTime() { return m_ulAlarmTime; } 311 | }; 312 | 313 | template 314 | class Timed : public Freq::Alarm 315 | { 316 | protected: 317 | Time m_Length; 318 | T m_Start; 319 | T m_End; 320 | public: 321 | //Timed(): 322 | // Alarm() 323 | //{ 324 | // m_Length = Time(0); 325 | //} 326 | //Timed(Time t, T start, T end): 327 | // Alarm() 328 | //{ 329 | // m_Length = Time(t); 330 | // set(t, start, end); 331 | //} 332 | explicit Timed(Timeline* timer): 333 | Alarm(timer), 334 | m_Length(Time(0)) 335 | {} 336 | 337 | Timed(const Timed&) = default; 338 | Timed(Timed&&) = default; 339 | Timed& operator=(const Timed&) = default; 340 | Timed& operator=(Timed&&) = default; 341 | 342 | //Timed(const Timed& t) { 343 | // m_Start = t.start(); 344 | // m_End = t.end(); 345 | // m_Length = t.length(); 346 | //} 347 | virtual ~Timed() {} 348 | T get() const{ 349 | return m_Start + (m_End - m_Start) * fraction(); 350 | } 351 | T inverse() const { 352 | return m_End - (m_End - m_Start) * fraction(); 353 | } 354 | T start() const{ 355 | return m_Start; 356 | } 357 | T end() const { 358 | return m_End; 359 | } 360 | T diff() const { 361 | return m_End - m_Start; 362 | } 363 | void restart() { 364 | static_cast(this)->set(m_Length); 365 | } 366 | void set(Time t, T start, T end) { 367 | m_Start = start; 368 | m_End = end; 369 | m_Length = Time(t); 370 | static_cast(this)->set(t); 371 | } 372 | void clear(T val) { 373 | m_Start = m_End = val; 374 | m_Length = Time(0); 375 | static_cast(this)->set(Time(m_Length)); 376 | } 377 | void shift(T val){ 378 | //m_Start = m_End = (m_End + val); 379 | //m_Length = Time(0); 380 | //static_cast(this)->set(Time(m_Length)); 381 | 382 | m_Start += val; 383 | m_End += val; 384 | } 385 | void finish(){ 386 | m_Start = m_End; 387 | static_cast(this)->set(Time(0)); 388 | } 389 | void reverse(){ 390 | std::swap(m_Start, m_End); 391 | static_cast(this)->set(m_Length); 392 | } 393 | }; 394 | 395 | private: 396 | 397 | Timeline m_globalTimeline; 398 | //unsigned long m_uiLastMark; 399 | unsigned long m_ulStartTime; 400 | unsigned int m_uiMinTick; 401 | 402 | public: 403 | 404 | Freq(): 405 | m_ulStartTime(get_ticks()), 406 | m_uiMinTick(1) 407 | {} 408 | 409 | unsigned long get_ticks() const { 410 | return K_GET_TICKS(); 411 | } 412 | float get_seconds() const { 413 | return K_GET_TICKS() * 0.001f; 414 | } 415 | 416 | // TODO: not yet implemented 417 | //void pause(); 418 | //void unpause(); 419 | 420 | //bool tick(); 421 | 422 | // accumulates time passed, until enough has passed to advance forward 423 | // may advance more than 1 frame's worth of time 424 | // returns # of ms to advance 425 | Freq::Time tick() { 426 | unsigned long ticks = get_ticks() - m_ulStartTime; 427 | //LOGf("start time: %s", m_ulStartTime); 428 | //LOGf("get_ticks(): %s", get_ticks()); 429 | //LOGf("ticks: %s", ticks); 430 | //LOGf("global timeline: %s", m_globalTimeline.ms()); 431 | unsigned int advance = (unsigned int)(ticks - m_globalTimeline.ms()); 432 | //LOGf("advance: %s", advance); 433 | if(advance >= m_uiMinTick) { 434 | m_globalTimeline.logic(Freq::Time::ms(advance)); 435 | //LOGf("advance post-logic: %s", advance); 436 | return Freq::Time::ms(advance); 437 | } 438 | return Freq::Time::ms(0); 439 | } 440 | 441 | Timeline* timeline() { return &m_globalTimeline; } 442 | }; 443 | 444 | #endif 445 | 446 | -------------------------------------------------------------------------------- /kit/fs/fs.h: -------------------------------------------------------------------------------- 1 | #ifndef FS_H_C8OF7L4K 2 | #define FS_H_C8OF7L4K 3 | 4 | #include 5 | #include 6 | #include "../log/log.h" 7 | 8 | #ifdef _WIN32 9 | static const std::string PATH_SEP = "\\"; 10 | #else 11 | static const std::string PATH_SEP = "/"; 12 | #endif 13 | 14 | namespace kit { 15 | 16 | inline std::string homedir() { 17 | const char* homedir = getenv("HOME"); 18 | if(!homedir) 19 | homedir = getenv("HOMEPATH"); 20 | if(!homedir) 21 | K_ERROR(GENERAL, "Unable to locate home directory."); 22 | return homedir; 23 | } 24 | 25 | inline std::string configdir(std::string apppath = std::string()) { 26 | std::string r; 27 | bool add_config=false; 28 | const char* cdir = getenv("XDG_CONFIG_PATH"); 29 | if(!cdir){ 30 | cdir = getenv("HOME"); 31 | add_config=true; 32 | } 33 | if(!cdir){ 34 | cdir = getenv("HOMEPATH"); 35 | add_config=true; 36 | } 37 | if(!cdir){ 38 | K_ERROR(GENERAL, "Unable to locate home directory."); 39 | } 40 | if(add_config) 41 | { 42 | #ifdef _WIN32 43 | r = std::string(cdir); 44 | #else 45 | r = std::string(cdir) + PATH_SEP + ".config"; 46 | #endif 47 | } 48 | else 49 | r = cdir; 50 | if(not apppath.empty()) 51 | return r + PATH_SEP + apppath; 52 | return r; 53 | } 54 | } 55 | 56 | #endif 57 | 58 | -------------------------------------------------------------------------------- /kit/kit_compat.h: -------------------------------------------------------------------------------- 1 | #ifndef _KIT_COMPAT_H 2 | 3 | #ifdef _MSC_VER 4 | #ifndef __WIN32__ 5 | #define __WIN32__ 6 | #endif 7 | #endif 8 | 9 | #ifdef __WIN32__ 10 | #define WIN32_LEAN_AND_MEAN 11 | #define NOMINMAX 12 | #include 13 | #endif 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /kit/log/errors.h: -------------------------------------------------------------------------------- 1 | #ifndef _ERRORS_DEF_HGR1PXJD 2 | #define _ERRORS_DEF_HGR1PXJD 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | enum class ErrorCode: unsigned 10 | { 11 | UNKNOWN = 0, 12 | 13 | GENERAL, 14 | INIT, 15 | LIBRARY, 16 | VERSION, 17 | 18 | READ, 19 | WRITE, 20 | PARSE, 21 | ACTION, 22 | 23 | LOCK, 24 | FATAL, 25 | TEST, 26 | TIMEOUT, 27 | 28 | MAX 29 | }; 30 | 31 | static const std::string g_ErrorString[] = { 32 | "Unknown error", 33 | 34 | "", 35 | "Unable to initialize", 36 | "Failed to load library", 37 | "Version mismatch", 38 | 39 | "Unable to read", 40 | "Unable to write", 41 | "Parse error", 42 | "Failed to perform action", 43 | 44 | "Failed to obtain lock", 45 | "Fatal error occurred", 46 | "Failed test", 47 | "Timeout" 48 | }; 49 | 50 | class Error: 51 | virtual public std::runtime_error 52 | { 53 | private: 54 | std::string m_Msg; 55 | public: 56 | //Error(ErrorCode code = ErrorCode::UNKNOWN, const char* text): 57 | // std::runtime_error(g_ErrorString[(int)code]) 58 | //{ 59 | //} 60 | Error(ErrorCode code = ErrorCode::UNKNOWN, const std::string& desc = std::string()): 61 | std::runtime_error(g_ErrorString[(unsigned)code]) 62 | { 63 | assert(code < ErrorCode::MAX); 64 | 65 | if(code != ErrorCode::UNKNOWN) 66 | { 67 | if(!desc.empty()) 68 | { 69 | if(!g_ErrorString[(unsigned)code].empty()) 70 | m_Msg = g_ErrorString[(unsigned)code] + ": " + desc; 71 | else 72 | m_Msg = desc; 73 | } 74 | else 75 | m_Msg = "Unknown Error"; 76 | } 77 | else if(code == ErrorCode::FATAL) 78 | { 79 | //assert(false); 80 | } 81 | else if(!desc.empty()) 82 | m_Msg = g_ErrorString[(unsigned)code]; 83 | 84 | //std::cout << m_Msg << std::endl; 85 | } 86 | const char* what() const noexcept override { 87 | if(!m_Msg.empty()) 88 | return m_Msg.c_str(); 89 | else 90 | return std::runtime_error::what(); 91 | } 92 | virtual ~Error() noexcept {} 93 | }; 94 | 95 | #endif 96 | 97 | -------------------------------------------------------------------------------- /kit/log/log.cpp: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | //#include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | //const int LOG_LENGTH = 256; 8 | //const std::string LOG_FILE = "log.txt"; 9 | 10 | Log::Log() 11 | { 12 | //m_cbErrors.set_capacity(LOG_LENGTH); 13 | //m_LogFile.open(LOG_FILE, ios_base::trunc); 14 | //ios::sync_with_stdio(false); 15 | } 16 | 17 | void Log::write(std::string s, Log::Level lev) 18 | { 19 | auto l = lock(); 20 | std::function after_unlock; 21 | { 22 | auto& th = this_log(); 23 | 24 | if(th.m_SilenceFlags == Log::Silencer::ALL) 25 | return; 26 | 27 | 28 | if(!th.m_bCapture) 29 | { 30 | ostringstream line; 31 | if(lev == Log::LL_ERROR) 32 | { 33 | for(unsigned i=0;ion_log(s,lev); 44 | }; 45 | } 46 | else 47 | { 48 | // apparently no capturing of non-error messages yet 49 | if(lev == LL_WARNING && 50 | (th.m_SilenceFlags & Log::Silencer::WARNINGS)) 51 | return; 52 | 53 | if(lev == LL_INFO && 54 | (th.m_SilenceFlags & Log::Silencer::INFO)) 55 | return; 56 | 57 | for(unsigned i=0;ion_log(s,lev); 67 | }; 68 | } 69 | } 70 | } 71 | 72 | if(after_unlock) 73 | after_unlock(); 74 | 75 | //else if(lev == LL_DEBUG) 76 | // cout << "[DEBUG] "; 77 | 78 | //if(m_LogFile.is_open()) 79 | //{ 80 | // for(unsigned i=0;i lines_in; 95 | vector lines_out; 96 | boost::split(lines_in, s, boost::is_any_of("\n")); 97 | for(auto&& line: lines_in) 98 | { 99 | if(boost::starts_with(line, "(")) 100 | { 101 | const std::string end_prefix = "): "; 102 | auto itr = line.find(end_prefix); 103 | if(itr != string::npos) 104 | line = line.substr(itr + end_prefix.length()); 105 | } 106 | lines_out.push_back(line); 107 | } 108 | return boost::join(lines_out, "\n"); 109 | } 110 | 111 | -------------------------------------------------------------------------------- /kit/log/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H_AB4CGIE6 2 | #define LOG_H_AB4CGIE6 3 | 4 | #include 5 | #include 6 | #include 7 | //#include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "errors.h" 16 | #include "../kit.h" 17 | 18 | //#define USE_THREAD_LOCAL 19 | 20 | class Log: 21 | public kit::singleton, 22 | public kit::mutexed 23 | { 24 | public: 25 | 26 | enum Level { LL_BLANK, LL_INFO, LL_WARNING, LL_ERROR }; 27 | 28 | class Message 29 | { 30 | public: 31 | std::string sMessage; 32 | Level eLevel; 33 | 34 | Message(std::string message, Level level): 35 | sMessage(message), 36 | eLevel(level) {} 37 | }; 38 | 39 | // Data specific to each thread 40 | class LogForwarder { 41 | public: 42 | LogForwarder(std::function cb) { 45 | auto& th = Log::get().this_log(); 46 | con = th.on_log.connect(move(cb)); 47 | } 48 | 49 | boost::signals2::scoped_connection con; 50 | }; 51 | 52 | class LogThread { 53 | public: 54 | boost::signals2::signal< 55 | void(std::string, Log::Level) 56 | > on_log; 57 | unsigned m_Indents = 0; 58 | unsigned m_SilenceFlags = 0; 59 | bool m_bCapture = false; 60 | std::vector m_Captured; 61 | bool empty() const { 62 | return !m_Indents && 63 | !m_SilenceFlags && 64 | !m_bCapture && 65 | m_Captured.empty(); 66 | } 67 | }; 68 | 69 | // Capturing output ignores silencers 70 | class Capturer 71 | { 72 | public: 73 | enum Flags { 74 | PAUSE_CAPTURE = 0, 75 | ERRORS = kit::bit(0) 76 | }; 77 | Capturer(Flags flags = ERRORS){ 78 | //auto l = Log::get().lock(); 79 | m_bOldValue = Log::get().capture(); 80 | Log::get().capture(flags ? true : false); 81 | } 82 | ~Capturer(){ 83 | //auto l = Log::get().lock(); 84 | if(!m_bDisable) { 85 | Log::get().capture(m_bOldValue); 86 | if(!m_bOldValue) 87 | Log::get().emit(); 88 | } 89 | } 90 | std::string emit() const { 91 | return Log::get().emit(); 92 | } 93 | private: 94 | bool m_bOldValue; 95 | bool m_bDisable = false; /// TODO: for move ctors 96 | }; 97 | 98 | class Silencer { 99 | public: 100 | enum Flags { 101 | UNSILENCE = 0, 102 | INFO = kit::bit(0), 103 | WARNINGS = kit::bit(1), 104 | ERRORS = kit::bit(2), 105 | ALL = kit::mask(3) 106 | }; 107 | Silencer(unsigned flags = ALL) { 108 | auto l = Log::get().lock(); 109 | m_OldFlags = Log::get().silenced(); 110 | Log::get().silence(flags); 111 | } 112 | ~Silencer(){ 113 | if(!m_bDisable) { 114 | //if(!m_OldFlags) 115 | Log::get().silence(m_OldFlags); 116 | } 117 | } 118 | private: 119 | unsigned m_OldFlags; 120 | bool m_bDisable = false; // TODO; for move ctor 121 | }; 122 | 123 | // Scoped indentation 124 | class Indent: 125 | boost::noncopyable 126 | { 127 | private: 128 | std::string m_ExitMsg; 129 | bool m_bPushed = false; 130 | public: 131 | Indent(std::nullptr_t) {} 132 | Indent( 133 | const std::string& msg = std::string(), 134 | const std::string& exit_msg = std::string() 135 | ): 136 | m_ExitMsg(exit_msg) 137 | { 138 | push(msg); 139 | } 140 | Indent& operator=(Indent&& rhs) { 141 | m_ExitMsg = std::move(rhs.m_ExitMsg); 142 | m_bPushed = rhs.m_bPushed; 143 | rhs.m_bPushed = false; 144 | return *this; 145 | } 146 | Indent(Indent&& rhs): 147 | m_ExitMsg(std::move(rhs.m_ExitMsg)), 148 | m_bPushed(std::move(rhs.m_bPushed)) 149 | { 150 | rhs.m_bPushed = false; 151 | } 152 | operator bool() const { 153 | return m_bPushed; 154 | } 155 | void push(std::string msg) { 156 | if(!m_bPushed) { 157 | m_bPushed = true; 158 | if(!msg.empty()) 159 | Log::get().write(std::move(msg)); 160 | Log::get().push_indent(); 161 | } 162 | } 163 | ~Indent(){ 164 | try{ 165 | pop(); 166 | }catch(...) {} 167 | } 168 | void pop() { 169 | if(m_bPushed) { 170 | m_bPushed = false; 171 | Log::get().pop_indent(); 172 | if(!m_ExitMsg.empty()) 173 | Log::get().write(move(m_ExitMsg)); 174 | } 175 | } 176 | unsigned level() const { 177 | return Log::get().indents(); 178 | } 179 | }; 180 | 181 | //static Log& get() 182 | //{ 183 | // static Log log; 184 | // return log; 185 | //} 186 | 187 | Log(); 188 | virtual ~Log() {} 189 | 190 | boost::signals2::signal< 191 | void(std::string, Log::Level) 192 | > on_log; 193 | 194 | std::string emit() { 195 | auto l = lock(); 196 | std::ostringstream oss; 197 | auto& captured = this_log().m_Captured; 198 | for(auto&& line: captured) 199 | oss << line << "\n"; 200 | captured.clear(); 201 | optimize(); 202 | std::string s = oss.str(); 203 | if(boost::ends_with(s, "\n")) 204 | s.pop_back(); 205 | return s; 206 | } 207 | void clear_capture() { 208 | auto l = lock(); 209 | this_log().m_Captured.clear(); 210 | optimize(); 211 | } 212 | bool capture() const { 213 | auto l = lock(); 214 | return this_log().m_bCapture; 215 | } 216 | void capture(bool b) { 217 | auto l = lock(); 218 | this_log().m_bCapture = b; 219 | } 220 | 221 | void push_indent() { 222 | auto l = lock(); 223 | ++this_log().m_Indents; 224 | } 225 | void pop_indent() { 226 | auto l = lock(); 227 | auto& indents = this_log().m_Indents; 228 | //auto indent = m_Indents.find(std::this_thread::get_id()); 229 | assert(indents > 0); 230 | --(indents); 231 | if(!indents) 232 | optimize(); 233 | } 234 | unsigned indents() const { 235 | #ifdef USE_THREAD_LOCAL 236 | assert(false); 237 | return 0; 238 | #else 239 | auto l = lock(); 240 | auto itr = m_Threads.find(std::this_thread::get_id()); 241 | if(itr != m_Threads.end()) 242 | return itr->second.m_Indents; 243 | else 244 | return 0; 245 | #endif 246 | } 247 | 248 | void write(std::string s, Level lev = LL_INFO); 249 | void warn(std::string s) { write(s,LL_WARNING); } 250 | void error(std::string s) {write(s,LL_ERROR);} 251 | 252 | //unsigned int size() const { return m_cbLog.size(); } 253 | //bool empty() const { return (size()==0); } 254 | void silence(unsigned flags = Silencer::ALL) { 255 | auto l = lock(); 256 | this_log().m_SilenceFlags = flags; 257 | if(!flags) 258 | optimize(); 259 | } 260 | unsigned silenced() const { 261 | auto l = lock(); 262 | return this_log().m_SilenceFlags; 263 | } 264 | void unsilence() { 265 | auto l = lock(); 266 | this_log().m_SilenceFlags = 0; 267 | optimize(); 268 | } 269 | 270 | unsigned num_threads() const { 271 | #ifndef USE_THREAD_LOCAL 272 | auto l = lock(); 273 | return m_Threads.size(); 274 | #endif 275 | return 0; 276 | } 277 | 278 | //void clear_threads() { 279 | // auto l = lock(); 280 | // m_Threads.clear(); 281 | //} 282 | 283 | void clear_indents() { 284 | auto l = lock(); 285 | this_log().m_Indents = 0; 286 | optimize(); 287 | } 288 | 289 | //boost::circular_buffer* getBuffer() { return &m_cbLog; } 290 | //const boost::circular_buffer* getBuffer_c() const { return &m_cbLog; } 291 | //std::string message(unsigned int idx) { 292 | // if(idx < m_cbLog.size()) 293 | // return m_cbLog.at(idx).sMessage; 294 | // return ""; 295 | //} 296 | 297 | LogThread& this_log() const { 298 | #ifdef USE_THREAD_LOCAL 299 | thread_local LogThread th; 300 | return th; 301 | #else 302 | auto l = lock(); 303 | return m_Threads[std::this_thread::get_id()]; 304 | #endif 305 | } 306 | 307 | static std::string consume_prefix(std::string s); 308 | 309 | private: 310 | 311 | void optimize() { 312 | #ifndef USE_THREAD_LOCAL 313 | //auto l = lock(); // already locked 314 | for(auto itr = m_Threads.begin(); 315 | itr != m_Threads.end();) 316 | { 317 | if(itr->second.empty()) 318 | itr = m_Threads.erase(itr); 319 | else 320 | ++itr; 321 | } 322 | #endif 323 | } 324 | 325 | //boost::circular_buffer m_cbLog; 326 | std::ofstream m_LogFile; 327 | //bool m_bSilence = false; 328 | //bool m_bCapture = false; 329 | //std::vector m_Captured; 330 | 331 | static const int DEFAULT_LENGTH = 256; 332 | //unsigned m_Indent = 0; 333 | 334 | //std::unordered_map m_Indents; 335 | #ifndef USE_THREAD_LOCAL 336 | mutable std::unordered_map m_Threads; 337 | #endif 338 | }; 339 | 340 | #ifdef DEBUG 341 | #define LOG_FORMAT boost::format("(%s:%s): %s") 342 | #define ERR_FORMAT boost::format("%s (%s:%s): %s") 343 | #define DEBUG_OPTIONAL(X) X 344 | #else 345 | #define LOG_FORMAT boost::format("%s") 346 | #define ERR_FORMAT boost::format("%s: %s") 347 | #define DEBUG_OPTIONAL(X) 348 | #endif 349 | 350 | //#define TRACE(X) {\ 351 | // auto msg = (TRACE_FORMAT %\ 352 | // DEBUG_OPTIONAL(__FILE__ % __LINE__ %)\ 353 | // std::string(X)\ 354 | // ).str();\ 355 | // Log::get().write(msg);\ 356 | //} 357 | //#define TRACEf(X,Y) {\ 358 | // auto msg = (TRACE_FORMAT %\ 359 | // DEBUG_OPTIONAL(__FILE__ % __LINE__ %)\ 360 | // (boost::format(X) % Y).str()\ 361 | // ).str();\ 362 | // Log::get().write(msg);\ 363 | //} 364 | #define LOG(X) {\ 365 | auto _msg = (LOG_FORMAT %\ 366 | DEBUG_OPTIONAL(__FILE__ % __LINE__ %)\ 367 | std::string(X)\ 368 | ).str();\ 369 | Log::get().write(_msg);\ 370 | } 371 | #define LOGf(X,Y) {\ 372 | auto _msg = (LOG_FORMAT %\ 373 | DEBUG_OPTIONAL(__FILE__ % __LINE__ %)\ 374 | (boost::format(X) % Y).str()\ 375 | ).str();\ 376 | Log::get().write(_msg);\ 377 | } 378 | #define WARNING(X) {\ 379 | auto _msg = (LOG_FORMAT %\ 380 | DEBUG_OPTIONAL(__FILE__ % __LINE__ %)\ 381 | std::string(X)\ 382 | ).str();\ 383 | Log::get().warn(_msg);\ 384 | } 385 | #define WARNINGf(X,Y) {\ 386 | auto _msg = (LOG_FORMAT %\ 387 | DEBUG_OPTIONAL(__FILE__ % __LINE__ %)\ 388 | (boost::format(X) % Y).str()\ 389 | ).str();\ 390 | Log::get().warn(_msg);\ 391 | } 392 | #define K_ERROR(CODE,X) {\ 393 | auto _msg = (ERR_FORMAT %\ 394 | g_ErrorString[(unsigned)ErrorCode::CODE] %\ 395 | DEBUG_OPTIONAL(__FILE__ % __LINE__ %)\ 396 | std::string(X)\ 397 | ).str();\ 398 | Log::get().error(_msg);\ 399 | throw Error(ErrorCode::CODE, _msg);\ 400 | } 401 | #define K_ERRORf(CODE,X,Y) {\ 402 | auto _msg = (ERR_FORMAT %\ 403 | g_ErrorString[(unsigned)ErrorCode::CODE] %\ 404 | DEBUG_OPTIONAL(__FILE__ % __LINE__ %)\ 405 | (boost::format(X) % Y).str()\ 406 | ).str();\ 407 | Log::get().error(_msg);\ 408 | throw Error(ErrorCode::CODE, _msg);\ 409 | } 410 | 411 | #ifdef _DEBUG 412 | #define DEBUG_LOG(X) LOG(X) 413 | #define DEBUG_LOGf(X,Y) LOGf(X,Y) 414 | #define DEBUG_WARNING(X) WARNING(X) 415 | #define DEBUG_WARNINGf(X,Y) WARNINGf(X,Y) 416 | // this will only write the error in debug, otherwise assert(false); 417 | #define DEBUG_ERROR(CODE,X) ERROR(CODE,X) 418 | #define DEBUG_ERRORf(CODE,X,Y) ERRORf(CODE,X,Y) 419 | #else 420 | #define DEBUG_LOG(X) 421 | #define DEBUG_LOGf(X,Y) 422 | #define DEBUG_WARNING(X) 423 | #define DEBUG_WARNINGf(X,Y) 424 | // this will only write the error in debug, otherwise assert(false); 425 | #define DEBUG_ERROR(CODE,X) assert(false); 426 | #define DEBUG_ERRORf(CODE,X,Y) assert(false); 427 | #endif 428 | 429 | 430 | //#ifdef DEBUG 431 | // #define _()\ 432 | // Log::Indent _li(\ 433 | // std::string(BOOST_CURRENT_FUNCTION)+" {",\ 434 | // "}"\ 435 | // );\ 436 | // Log::Indent _li2; 437 | //#else 438 | // #define _() 439 | //#endif 440 | 441 | #endif 442 | 443 | -------------------------------------------------------------------------------- /kit/math/angle.h: -------------------------------------------------------------------------------- 1 | #ifndef _ANGLE_H 2 | #define _ANGLE_H 3 | 4 | #include "common.h" 5 | #include 6 | 7 | #ifndef K_ANGLE_UNIT 8 | #define K_ANGLE_UNIT TURNS 9 | #endif 10 | 11 | class Angle 12 | { 13 | 14 | float m_fTurns; 15 | 16 | public: 17 | 18 | enum Type{ 19 | TURNS = 0, 20 | DEGREES, 21 | RADIANS 22 | }; 23 | 24 | Angle(float a = 0.0f, Type t = K_ANGLE_UNIT) 25 | { 26 | switch(t){ 27 | case DEGREES: 28 | m_fTurns = a; 29 | break; 30 | case RADIANS: 31 | m_fTurns = RAD2DEGf(a); 32 | break; 33 | case TURNS: 34 | m_fTurns = RAD2DEGf(a*K_TAU); 35 | break; 36 | default: 37 | assert(false); 38 | } 39 | wrap(); 40 | } 41 | 42 | //Angle(const glm::vec2& v) { 43 | // m_fTurns = glm::angle(Axis::X, glm::normalize(v)); 44 | //} 45 | 46 | glm::vec2 vector() const { 47 | return glm::vec2(Angle::cos(), Angle::sin()); 48 | } 49 | 50 | //static Angle between(const Angle& a, const Angle& b) { 51 | // return b-a; 52 | //} 53 | 54 | static Angle degrees(float deg) { 55 | return Angle(deg, DEGREES); 56 | } 57 | static Angle radians(float rad) { 58 | return Angle(RAD2DEGf(rad), RADIANS); 59 | } 60 | static Angle turns(float t) { 61 | return Angle(t * 360.0f, DEGREES); 62 | } 63 | 64 | void wrap() 65 | { 66 | while(m_fTurns >= 0.5f) 67 | m_fTurns -= 1.0f; 68 | while(m_fTurns < -0.5f) 69 | m_fTurns += 1.0f; 70 | } 71 | 72 | glm::vec2 vector(float mag = 1.0f) { 73 | float rad = m_fTurns*K_TAU; 74 | return glm::vec2( 75 | mag * std::cos(rad), 76 | mag * std::sin(rad) 77 | ); 78 | } 79 | 80 | float cos() const { 81 | return std::cos(DEG2RADf(m_fTurns)); 82 | } 83 | float sin() const { 84 | return std::sin(DEG2RADf(m_fTurns)); 85 | } 86 | 87 | Angle flip() const { 88 | return *this+Angle::turns(0.5f); 89 | } 90 | 91 | float degrees() const { return m_fTurns*360.0f; } 92 | float radians() const { return m_fTurns*K_TAU; } 93 | float turns() const { return DEG2RADf(m_fTurns*K_TAU); } 94 | //void degrees(float deg) { 95 | // m_fTurns = deg; 96 | // wrap(); 97 | //} 98 | //void radians(float rad) { 99 | // m_fTurns = RAD2DEGf(rad); 100 | // wrap(); 101 | //} 102 | 103 | Angle operator +(const Angle& a) const{ 104 | return Angle(m_fTurns + a.turns()); 105 | } 106 | const Angle& operator +=(const Angle& a) { 107 | return (*this = *this+a); 108 | } 109 | Angle operator -(const Angle& a) const{ 110 | return Angle(m_fTurns - a.turns()); 111 | } 112 | Angle operator -() const { 113 | return Angle(-m_fTurns); 114 | } 115 | Angle operator *(float f) const{ 116 | return Angle(m_fTurns * f); 117 | } 118 | const Angle& operator*=(float f) { 119 | return (*this = *this*f); 120 | } 121 | bool operator==(const Angle& a) const { 122 | return floatcmp(m_fTurns, a.turns()); 123 | } 124 | 125 | bool operator==(float f) const { 126 | return floatcmp(m_fTurns, Angle::turns(f).turns()); 127 | } 128 | bool operator!=(float f) const { 129 | return !floatcmp(m_fTurns, Angle::turns(f).turns()); 130 | } 131 | const Angle& operator=(float f) { 132 | m_fTurns = f; 133 | wrap(); 134 | return *this; 135 | } 136 | 137 | bool operator>(float f) const { 138 | return turns() > f; 139 | } 140 | bool operator>=(float f) const { 141 | return turns() >= f; 142 | } 143 | bool operator<(float f) const { 144 | return turns() < f; 145 | } 146 | bool operator<=(float f) const { 147 | return turns() <= f; 148 | } 149 | 150 | bool operator>(const Angle& a) const { 151 | return turns() > a.turns(); 152 | } 153 | bool operator>=(const Angle& a) const { 154 | return turns() >= a.turns(); 155 | } 156 | bool operator<(const Angle& a) const { 157 | return turns() < a.turns(); 158 | } 159 | bool operator<=(const Angle& a) const { 160 | return turns() <= a.turns(); 161 | } 162 | }; 163 | 164 | #endif 165 | 166 | -------------------------------------------------------------------------------- /kit/math/common.h: -------------------------------------------------------------------------------- 1 | #ifndef _MATHCOMMON_H_INCLUDED 2 | #define _MATHCOMMON_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifndef K_EPSILON 12 | #define K_EPSILON 0.00001 13 | #endif 14 | 15 | #ifndef K_PI 16 | #define K_PI 3.1415926535897932385 17 | #endif 18 | 19 | #ifndef K_TAU 20 | #define K_TAU 6.2831853071795864770 21 | #endif 22 | 23 | #define DEG2RAD(X) ((X)*K_TAU/360.0) 24 | #define RAD2DEG(X) ((X)*360.0/K_TAU) 25 | #define DEG2RADf(X) ((X)*((float)K_TAU)/360.0f) 26 | #define RAD2DEGf(X) ((X)*360.0f/((float)K_TAU)) 27 | 28 | #define RAD2TRNf(X) ((X)/(float)K_TAU) 29 | #define TRN2RADf(X) ((X)*(float)K_TAU) 30 | #define DEG2TRNf(X) ((X)/360.0f) 31 | #define TRN2DEGf(X) ((X)*360.0f) 32 | 33 | inline float sinT(float theta){ return sin(theta*K_TAU);} 34 | inline float cosT(float theta){ return cos(theta*K_TAU);} 35 | inline float tanT(float theta){ return tan(theta*K_TAU);} 36 | 37 | inline float sin_deg(float theta){ 38 | return (sinf(theta*((float)K_TAU/360.0f))); 39 | } 40 | inline float cos_deg(float theta){ 41 | return (cosf(theta*((float)K_TAU/360.0f))); 42 | } 43 | inline float tan_deg(float theta){ 44 | return (tanf(theta*((float)K_TAU/360.0f))); 45 | } 46 | 47 | inline double sin_deg(double theta){ 48 | return (sin(theta*(K_TAU/360.0))); 49 | } 50 | inline double cos_deg(double theta){ 51 | return (cos(theta*(K_TAU/360.0))); 52 | } 53 | inline double tan_deg(double theta){ 54 | return (tan(theta*(K_TAU/360.0))); 55 | } 56 | 57 | inline bool floatcmp(float a, float b){ 58 | return(fabs(a-b) < K_EPSILON); 59 | } 60 | 61 | template 62 | inline T abs(T x) 63 | { 64 | return x>0?x:-x; 65 | } 66 | 67 | template 68 | T sgn(T x) { 69 | return floatcmp(x,0.0)?0.0:(x>0.0?1.0:-1.0); 70 | } 71 | 72 | template 73 | inline T max_val(T a, T b) 74 | { 75 | return a>b?a:b; 76 | } 77 | 78 | // checks for absolute value, returns actual number 79 | template 80 | inline T max_abs(T a, T b) 81 | { 82 | if(abs(a) > abs(b)) 83 | return a; 84 | return b; 85 | } 86 | 87 | template 88 | inline T min_abs(T a, T b) 89 | { 90 | if(abs(a) < abs(b)) 91 | return a; 92 | return b; 93 | } 94 | 95 | 96 | template 97 | inline T clamp(T val, T low, T high) 98 | { 99 | return val < low ? low : (val > high ? high : val); 100 | } 101 | 102 | #define sq(a) ((a)*(a)) 103 | 104 | //#ifdef _MATRIX_ROW_MAJOR 105 | // #define _MATRIX_INDEX(m,i,j) (m)[i][j] 106 | // #define _MATRIX_INDEX_1D(m,i,j) (m)[j*4+i] 107 | //#else 108 | // #define _MATRIX_INDEX(m,i,j) (m)[j][i] 109 | // #define _MATRIX_INDEX_1D(m,i,j) (m)[i*4+j] 110 | //#endif 111 | 112 | inline bool IS_NAN(double f){ return f!=f; } 113 | inline bool IS_NAN(float f){ return f!=f; } 114 | 115 | namespace Axis{ 116 | const glm::vec3 ZERO(0.0f, 0.0f, 0.0f); 117 | const glm::vec3 ALL(1.0f, 1.0f, 1.0f); 118 | const glm::vec3 X(1.0f, 0.0f, 0.0f); 119 | const glm::vec3 Y(0.0f, 1.0f, 0.0f); 120 | const glm::vec3 Z(0.0f, 0.0f, 1.0f); 121 | const glm::vec3 XY(1.0f, 1.0f, 0.0f); 122 | const glm::vec3 YZ(0.0f, 1.0f, 1.0f); 123 | const glm::vec3 XZ(1.0f, 0.0f, 1.0f); 124 | const glm::vec3 nX(-1.0f, 0.0f, 0.0f); 125 | const glm::vec3 nY(0.0f, -1.0f, 0.0f); 126 | const glm::vec3 nZ(0.0f, 0.0f, -1.0f); 127 | } 128 | 129 | #include "matrixops.h" 130 | #include "vectorops.h" 131 | 132 | #endif 133 | 134 | -------------------------------------------------------------------------------- /kit/math/matrixops.h: -------------------------------------------------------------------------------- 1 | #ifndef _MATRIXOPS_H 2 | #define _MATRIXOPS_H 3 | 4 | #include "common.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // extra Matrix functions that aren't easily provided by glm 11 | 12 | namespace Matrix { 13 | 14 | inline float index(glm::mat4& m, unsigned int idx) { 15 | float* f = glm::value_ptr(m); 16 | return f[idx]; 17 | } 18 | 19 | inline void reset_orientation(glm::mat4& m) { 20 | float* f = glm::value_ptr(m); 21 | f[1]=f[2]=f[4]=f[5]=f[8]=f[9]=0.0f; 22 | f[0]=f[5]=f[10]=1.0f; 23 | } 24 | inline glm::mat3 orientation(const glm::mat4& m) { 25 | glm::mat3 r; 26 | float* rf = glm::value_ptr(r); 27 | const float* mf = glm::value_ptr(m); 28 | for(unsigned i=0;i<=2;++i) 29 | rf[i] = mf[i]; 30 | for(unsigned i=4;i<=6;++i) 31 | rf[i-1] = mf[i]; 32 | for(unsigned i=8;i<=10;++i) 33 | rf[i-2] = mf[i]; 34 | return r; 35 | } 36 | inline glm::vec3 translation(const glm::mat4& m){ 37 | const float* f = glm::value_ptr(m); 38 | return glm::vec3(f[12], f[13], f[14]); 39 | } 40 | inline void wScale(glm::mat4& m, float v = 1.0f) { 41 | float* f = glm::value_ptr(m); 42 | f[15] = v; 43 | } 44 | inline std::string to_string(const glm::mat4& m) { 45 | const float* f = glm::value_ptr(m); 46 | std::ostringstream ss; 47 | std::string s; 48 | for(int i=0; i<16; i++) { 49 | ss.str(""); 50 | ss << f[i]; 51 | s += (s.empty()?"":" ") + ss.str(); 52 | } 53 | return s; 54 | } 55 | //inline glm::vec3 check(const glm::mat4& m) { 56 | // const float* f = glm::value_ptr(m); 57 | // assert(floatcmp(f[3],0.0f)); 58 | // assert(floatcmp(f[7],0.0f)); 59 | // assert(floatcmp(f[11],0.0f)); 60 | // assert(floatcmp(f[15],1.0f)); 61 | //} 62 | inline void translation(glm::mat4& m, const glm::vec3& t){ 63 | float* f = glm::value_ptr(m); 64 | f[12] = t[0]; 65 | f[13] = t[1]; 66 | f[14] = t[2]; 67 | } 68 | inline void translate(glm::mat4& m, const glm::vec3& t) { 69 | float* f = glm::value_ptr(m); 70 | f[12] += t[0]; 71 | f[13] += t[1]; 72 | f[14] += t[2]; 73 | } 74 | inline void reset_translation(glm::mat4& m) { 75 | float* f = glm::value_ptr(m); 76 | f[12]=f[13]=f[14]=0.0f; 77 | } 78 | inline glm::mat4 with_translation(glm::mat4 m, glm::vec3 t) { 79 | float* f = glm::value_ptr(m); 80 | f[12] = t[0]; 81 | f[13] = t[1]; 82 | f[14] = t[2]; 83 | return m; 84 | } 85 | 86 | inline void scale(glm::mat4& m, float v) { 87 | m *= v; 88 | } 89 | inline void rescale(glm::mat4& m, float v = 1.0f) { 90 | auto vec = glm::vec3(v,v,v); 91 | auto pos = Matrix::translation(m); 92 | m = glm::scale(vec); 93 | Matrix::translate(m, pos); 94 | } 95 | inline void scale(glm::mat4& m, glm::vec3 v) { 96 | float* f = glm::value_ptr(m); 97 | m *= glm::vec4(v, 1.0f); 98 | } 99 | inline void rescale(glm::mat4& m, glm::vec3 v = glm::vec3(1.0f)) { 100 | auto pos = Matrix::translation(m); 101 | m = glm::scale(v); 102 | Matrix::translate(m, pos * v); 103 | } 104 | 105 | inline glm::vec3 scale(const glm::mat4& m) { 106 | const float* f = glm::value_ptr(m); 107 | glm::vec3 x = glm::vec3(f[0],f[1],f[2]); 108 | glm::vec3 y = glm::vec3(f[4],f[5],f[6]); 109 | glm::vec3 z = glm::vec3(f[8],f[9],f[10]); 110 | return glm::vec3(glm::length(x),glm::length(y),glm::length(z)); 111 | } 112 | 113 | inline glm::vec3 right(const glm::mat4& m) { 114 | const float* f = glm::value_ptr(m); 115 | return glm::vec3(f[0], f[1], f[2]); 116 | } 117 | inline glm::vec3 up(const glm::mat4& m) { 118 | const float* f = glm::value_ptr(m); 119 | return glm::vec3(f[4], f[5], f[6]); 120 | } 121 | inline glm::vec3 heading(const glm::mat4& m){ 122 | const float* f = glm::value_ptr(m); 123 | return -glm::vec3(f[8], f[9], f[10]); 124 | } 125 | 126 | // remember to normalize after computing these 127 | inline glm::vec3 upXZ(const glm::mat4& m) 128 | { 129 | glm::vec3 r = up(m); 130 | r.y = 0.0f; 131 | return glm::normalize(r); 132 | } 133 | inline glm::vec3 headingXZ(const glm::mat4& m) 134 | { 135 | glm::vec3 r = heading(m); 136 | r.y = 0.0f; 137 | return glm::normalize(r); 138 | } 139 | 140 | inline bool is_identity(const glm::mat4& m) 141 | { 142 | // glm takes care of floatcmp and epsilon value 143 | return m == glm::mat4(1.0f); 144 | } 145 | 146 | inline bool is_zero(const glm::mat4& m) 147 | { 148 | // glm takes care of floatcmp and epsilon value 149 | return m == glm::mat4(0.0f); 150 | } 151 | 152 | inline glm::vec3 mult(const glm::mat4& m, const glm::vec4& v) 153 | { 154 | auto r = m * v; 155 | return glm::vec3(r.x, r.y, r.z); 156 | } 157 | inline glm::vec3 mult(const glm::vec4& v, const glm::mat4& m) 158 | { 159 | auto r = v * m; 160 | return glm::vec3(r.x, r.y, r.z); 161 | } 162 | inline glm::vec3 mult(const glm::vec3& v, const glm::mat4& m) 163 | { 164 | auto r = glm::vec4(v, 1.0f) * m; 165 | return glm::vec3(r.x, r.y, r.z); 166 | } 167 | inline glm::vec3 mult(const glm::mat4& m, const glm::vec3& v) 168 | { 169 | auto r = m * glm::vec4(v, 1.0f); 170 | return glm::vec3(r.x,r.y,r.z); 171 | } 172 | 173 | inline glm::mat4 from_array(const float* f) 174 | { 175 | glm::mat4 m; 176 | float* m_array = glm::value_ptr(m); 177 | for(int i=0;i<16;i++) 178 | m_array[i] = f[i]; 179 | return m; 180 | } 181 | 182 | inline float* ptr(glm::mat4& m) { 183 | return glm::value_ptr(m); 184 | } 185 | inline const float* ptr(const glm::mat4& m) { 186 | return glm::value_ptr(m); 187 | } 188 | } 189 | 190 | #endif 191 | 192 | -------------------------------------------------------------------------------- /kit/math/matrixstack.cpp: -------------------------------------------------------------------------------- 1 | #include "matrixstack.h" 2 | #include "../kit.h" 3 | #include "common.h" 4 | #include 5 | 6 | MatrixStack :: MatrixStack() 7 | { 8 | //m_pHead = NULL; 9 | m_pHead = new Node(new glm::mat4(), NULL); 10 | } 11 | 12 | MatrixStack :: ~MatrixStack() 13 | { 14 | delete m_pHead; 15 | } 16 | 17 | void MatrixStack :: identity() 18 | { 19 | delete m_pHead; 20 | m_pHead = new Node(new glm::mat4(), NULL); 21 | } 22 | 23 | void MatrixStack :: clear() 24 | { 25 | delete m_pHead; 26 | m_pHead = NULL; 27 | } 28 | 29 | glm::mat4* MatrixStack :: push() 30 | { 31 | glm::mat4* new_matrix; 32 | if(!m_pHead) 33 | identity(); 34 | //if(!m_pHead) 35 | // new_matrix = new Matrix(Matrix::IDENTITY); 36 | //else 37 | new_matrix = new glm::mat4(*m_pHead->data); 38 | 39 | m_pHead = new Node(new_matrix, m_pHead); 40 | return m_pHead->data; 41 | } 42 | 43 | void MatrixStack :: push_post(const glm::mat4& m) 44 | { 45 | glm::mat4* new_matrix; 46 | if(!m_pHead) 47 | identity(); 48 | 49 | new_matrix = new glm::mat4(*m_pHead->data); 50 | *new_matrix *= m; 51 | 52 | m_pHead = new Node(new_matrix, m_pHead); 53 | } 54 | 55 | void MatrixStack :: push(const glm::mat4& m) 56 | { 57 | glm::mat4* new_matrix; 58 | if(!m_pHead) 59 | identity(); 60 | 61 | new_matrix = new glm::mat4(*m_pHead->data); 62 | *new_matrix = m * *new_matrix; 63 | 64 | m_pHead = new Node(new_matrix, m_pHead); 65 | } 66 | 67 | 68 | void MatrixStack :: push_inverse(const glm::mat4& m) 69 | { 70 | glm::mat4* new_matrix; 71 | if(!m_pHead) 72 | identity(); 73 | //new_matrix = new Matrix(Matrix::IDENTITY); 74 | 75 | new_matrix = new glm::mat4(*m_pHead->data); 76 | *new_matrix = glm::inverse(m) * *new_matrix; 77 | //*new_matrix *= glm::inverse(m); 78 | 79 | m_pHead = new Node(new_matrix, m_pHead); 80 | } 81 | 82 | void MatrixStack :: push_inverse_post(const glm::mat4& m) 83 | { 84 | glm::mat4* new_matrix; 85 | if(!m_pHead) 86 | identity(); 87 | 88 | new_matrix = new glm::mat4(*m_pHead->data); 89 | //*new_matrix *= Matrix(Matrix::INVERSE, m); 90 | //*new_matrix = glm::inverse(m) * *new_matrix; 91 | *new_matrix *= glm::inverse(m); 92 | 93 | m_pHead = new Node(new_matrix, m_pHead); 94 | } 95 | 96 | bool MatrixStack :: pop() 97 | { 98 | assert(m_pHead); 99 | if(!m_pHead) 100 | return false; 101 | 102 | Node* delete_me = m_pHead; 103 | Node* new_head = m_pHead->next; 104 | delete_me->next = NULL; // preserve 105 | m_pHead = new_head; 106 | delete delete_me; 107 | 108 | return true; 109 | } 110 | 111 | bool MatrixStack :: pop(glm::mat4& m) 112 | { 113 | assert(m_pHead); 114 | if(!m_pHead) 115 | return false; 116 | 117 | Node* delete_me = m_pHead; 118 | Node* new_head = m_pHead->next; 119 | delete_me->next = NULL; // preserve 120 | m_pHead = new_head; 121 | m = *delete_me->data; // grab popped data 122 | delete delete_me; 123 | 124 | return true; 125 | } 126 | 127 | unsigned int MatrixStack :: size() const 128 | { 129 | unsigned int count = 0; 130 | const Node* n = m_pHead; 131 | while(n) 132 | { 133 | ++count; 134 | n = n->next; 135 | } 136 | return count; 137 | } 138 | 139 | -------------------------------------------------------------------------------- /kit/math/matrixstack.h: -------------------------------------------------------------------------------- 1 | #ifndef MATRIXSTACK_H 2 | #define MATRIXSTACK_H 3 | 4 | //#include "matrix.h" 5 | #include "common.h" 6 | 7 | // Example: 8 | // matrixstack.push()->translate(Vector3(1.0, 2.0, 3.0f); 9 | // to grab matrix at any time, use *matrixstack.top() 10 | // matrixstack.pop(); 11 | 12 | class MatrixStack 13 | { 14 | public: 15 | struct Node { 16 | Node(glm::mat4* _data = NULL, Node* _next = NULL) { 17 | data = _data; 18 | next = _next; 19 | } 20 | ~Node(){ 21 | delete data; 22 | delete next; 23 | } 24 | glm::mat4* data; 25 | Node* next; 26 | }; 27 | 28 | class ScopedPop 29 | { 30 | public: 31 | explicit ScopedPop(MatrixStack& ms): 32 | m_Stack(&ms) 33 | {} 34 | ~ScopedPop() { 35 | if(m_Stack) 36 | m_Stack->pop(); 37 | } 38 | private: 39 | MatrixStack* m_Stack; 40 | }; 41 | private: 42 | Node* m_pHead; 43 | public: 44 | MatrixStack(); 45 | virtual ~MatrixStack(); 46 | void clear(); 47 | void identity(); 48 | glm::mat4* push(); 49 | void push(const glm::mat4& m); 50 | void push_post(const glm::mat4& m); 51 | void push_inverse(const glm::mat4& m); 52 | void push_inverse_post(const glm::mat4& m); 53 | bool pop(); 54 | bool pop(glm::mat4& m); 55 | unsigned int size() const; 56 | bool empty() { return m_pHead!=NULL; } 57 | const glm::mat4* top_c() const { 58 | if(!m_pHead) 59 | return NULL; 60 | return m_pHead->data; 61 | } 62 | glm::mat4* top() { 63 | if(!m_pHead) 64 | return NULL; 65 | return m_pHead->data; 66 | } 67 | //glm::mat4 calc_inverse() { 68 | // if(m_pHead) 69 | // return glm::inverse(*top()); 70 | // else 71 | // return glm::mat4(); 72 | //} 73 | //glm::mat4 calc_inverse2() { 74 | // if(m_pHead) 75 | // { 76 | // glm::mat4 m = glm::inverse(*top()); 77 | // Vector3 t = top()->translation(); 78 | // m.setTranslation(-t); 79 | // return m; 80 | // } 81 | // else 82 | // return glm::mat4(1.0f); 83 | //} 84 | }; 85 | 86 | #endif 87 | 88 | -------------------------------------------------------------------------------- /kit/math/vectorops.h: -------------------------------------------------------------------------------- 1 | #ifndef VECTOROPS_H 2 | #define VECTOROPS_H 3 | 4 | ////#include "common.h" 5 | //#include 6 | //#include 7 | #include 8 | 9 | namespace Vector 10 | { 11 | // inline bool isZero2(const glm::vec2& v) { 12 | // if( floatcmp(v.x, 0.0f) && 13 | // floatcmp(v.y, 0.0f)) 14 | // return true; 15 | // return false; 16 | // } 17 | // inline bool isZero(const glm::vec3& v) { 18 | // if( floatcmp(v.x, 0.0f) && 19 | // floatcmp(v.y, 0.0f) && 20 | // floatcmp(v.z, 0.0f)) 21 | // return true; 22 | // return false; 23 | // } 24 | inline std::string to_string(const glm::vec2& v) { 25 | return std::string("(") + 26 | std::to_string(v.x) + ", " + 27 | std::to_string(v.y) + ")"; 28 | } 29 | inline std::string to_string(const glm::vec3& v) { 30 | return std::string("(") + 31 | std::to_string(v.x) + ", " + 32 | std::to_string(v.y) + ", " + 33 | std::to_string(v.z) + ")"; 34 | } 35 | inline std::string to_string(const glm::vec4& v) { 36 | return std::string("(") + 37 | std::to_string(v.x) + ", " + 38 | std::to_string(v.y) + ", " + 39 | std::to_string(v.z) + ", " + 40 | std::to_string(v.w) + ")"; 41 | } 42 | 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /kit/meta/schema.h: -------------------------------------------------------------------------------- 1 | #ifndef _KIT_SCHEMA_H_PQUEFVOM 2 | #define _KIT_SCHEMA_H_PQUEFVOM 3 | 4 | #include 5 | #include "../meta/meta.h" 6 | #include "../log/log.h" 7 | #include 8 | 9 | template class Ptr, template class This> 10 | class SchemaBase 11 | { 12 | public: 13 | using mutex_type = Mutex; 14 | 15 | SchemaBase() = default; 16 | 17 | SchemaBase(const Ptr>& m); 18 | 19 | SchemaBase(const std::string& fn): 20 | SchemaBase(kit::make>>(fn)) 21 | {} 22 | 23 | // validate throws on error, check just returns false 24 | template class TPtr, template class TThis> 25 | void validate(const TPtr>& m) const; 26 | 27 | template class TPtr, template class TThis> 28 | bool check(const TPtr>& m) const { 29 | try{ 30 | Log::Silencer ls; 31 | validate(m); 32 | return true; 33 | }catch(Error&){ 34 | }catch(std::out_of_range&){ 35 | }catch(boost::bad_any_cast&){ 36 | } 37 | return false; 38 | } 39 | 40 | // adds all schema fields with default values 41 | template class TPtr, template class TThis> 42 | void add_missing_fields(TPtr>& m) const; 43 | 44 | // ignores fields marked as optional 45 | template class TPtr, template class TThis> 46 | void add_required_fields(TPtr>& m) const; 47 | 48 | template class TPtr, template class TThis> 49 | TThis> make_default() const; 50 | 51 | Ptr> meta() { 52 | return m_pSchema; 53 | } 54 | Ptr> meta() const { 55 | return m_pSchema; 56 | } 57 | 58 | private: 59 | Ptr> m_pSchema; 60 | }; 61 | 62 | using Schema = SchemaBase; 63 | using SchemaL = SchemaBase; 64 | using SchemaS = SchemaBase; 65 | using SchemaMT = SchemaBase; 66 | 67 | #include "schema.inl" 68 | 69 | #endif 70 | 71 | -------------------------------------------------------------------------------- /kit/meta/schema.inl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "schema.h" 7 | 8 | template class Ptr, template class This> 9 | SchemaBase :: SchemaBase(const Ptr>& m): 10 | m_pSchema(m) 11 | { 12 | assert(m); 13 | } 14 | 15 | template class Ptr, template class This> 16 | template class TPtr, template class TThis> 17 | void SchemaBase :: validate(const TPtr>& m) const 18 | { 19 | // our stack for keeping track of location in tree while iterating 20 | std::deque>, 22 | std::unique_lock, 23 | std::string 24 | >> metastack; 25 | 26 | // TODO: loop thru schema, look up required fields in m 27 | 28 | m->each_c([this, &metastack]( 29 | //Ptr> item, 30 | TPtr> parent, 31 | const MetaElement& e, 32 | unsigned level 33 | ){ 34 | //LOGf("level: %s, key: %s", level % e.key); 35 | //if(boost::algorithm::starts_with(e.key, ".")) 36 | //{ 37 | 38 | //} 39 | 40 | if(e.type.id != MetaType::ID::META) { 41 | 42 | std::vector path; 43 | for(auto&& d: metastack) { 44 | std::string key = std::get<2>(d); 45 | if(!key.empty()) 46 | path.push_back(std::get<2>(d)); 47 | } 48 | path.push_back(e.key); 49 | if(path.empty()) 50 | return MetaLoop::CONTINUE; 51 | 52 | //LOGf("path element: %s", boost::algorithm::join(path,"/")); 53 | 54 | //Ptr> r; 55 | try{ 56 | auto schema_metadata = m_pSchema->path(path); 57 | bool valid_value = false; 58 | //LOGf("json: %s", schema_metadata->serialize(MetaFormat::JSON)); 59 | for(auto&& val: *schema_metadata->meta(".values")) { 60 | //LOGf("key: %s", e.key); 61 | if(kit::any_eq(val.value, e.value)) { 62 | valid_value = true; 63 | break; 64 | } 65 | } 66 | if(!valid_value) 67 | K_ERROR(PARSE, "schema validation failed"); 68 | 69 | //}catch(const boost::bad_any_cast&){ 70 | }catch(const std::out_of_range&){ 71 | } 72 | 73 | // metastack -> path 74 | // path -> schema format 75 | // look up path->at(".values")->have(element) in schema 76 | // alternative: dictionary with key=".value" value 77 | // 78 | // example: 79 | // { 80 | // "pick_a_number": { 81 | // // first way 82 | // ".values":[ 83 | // 1,2,3 84 | // ] 85 | // // second way 86 | // ".values:[ 87 | // {".value": 1}, 88 | // {".value": 3}, 89 | // {".value": 2} 90 | // ] 91 | // } 92 | // Should be able to mix and match the above ^ 93 | // 94 | // TODO: other hints: 95 | // .min/gte, .max/lte, .gt, .lt 96 | // 97 | // throw out_of_range if not 98 | } 99 | 100 | return MetaLoop::STEP; 101 | }, 102 | (unsigned)Meta::EachFlag::DEFAULTS | 103 | (unsigned)Meta::EachFlag::RECURSIVE, 104 | &metastack 105 | ); 106 | } 107 | 108 | template class Ptr, template class This> 109 | template class TPtr, template class TThis> 110 | void SchemaBase :: add_missing_fields(TPtr>& m) const 111 | { 112 | assert(false); 113 | } 114 | 115 | template class Ptr, template class This> 116 | template class TPtr, template class TThis> 117 | void SchemaBase :: add_required_fields(TPtr>& m) const 118 | { 119 | assert(false); 120 | } 121 | 122 | 123 | template class Ptr, template class This> 124 | template class TPtr, template class TThis> 125 | TThis> SchemaBase :: make_default() const 126 | { 127 | return TPtr>(); 128 | } 129 | 130 | -------------------------------------------------------------------------------- /kit/reactive/reactive.h: -------------------------------------------------------------------------------- 1 | #ifndef REACTIVE_H_GQBDAQXV 2 | #define REACTIVE_H_GQBDAQXV 3 | 4 | #include 5 | #include 6 | #include 7 | #include "../kit.h" 8 | #include "signal.h" 9 | 10 | namespace kit 11 | { 12 | //template> 13 | template> 14 | class reactive 15 | { 16 | public: 17 | 18 | reactive() = default; 19 | reactive(const T& t): 20 | m_Data(t) 21 | { 22 | on_change(m_Data); 23 | } 24 | reactive(T&& t): 25 | m_Data(t) 26 | { 27 | on_change(m_Data); 28 | } 29 | 30 | T& operator=(const T& t) 31 | { 32 | m_Data = t; 33 | on_change(t); 34 | return m_Data; 35 | } 36 | T& operator=(T&& t) 37 | { 38 | m_Data = t; 39 | on_change(m_Data); 40 | return m_Data; 41 | } 42 | 43 | T operator*() { 44 | return m_Data; 45 | } 46 | const T& operator*() const { 47 | return m_Data; 48 | } 49 | operator T() const { 50 | return m_Data; 51 | } 52 | T& get() { 53 | return m_Data; 54 | } 55 | const T& get() const { 56 | return m_Data; 57 | } 58 | 59 | void trigger(){ 60 | on_change(m_Data); 61 | } 62 | 63 | void clear() 64 | { 65 | //on_destroy(); 66 | on_change.clear(); 67 | //on_change.disconnect_all_slots(); 68 | //on_destroy.disconnect_all_slots(); 69 | } 70 | 71 | Signal on_change; 72 | //boost::signals2::signal on_destroy; 73 | 74 | private: 75 | 76 | T m_Data = T(); 77 | }; 78 | 79 | template 80 | class lazy 81 | { 82 | public: 83 | 84 | lazy() = default; 85 | lazy(const lazy&) = default; 86 | lazy(std::function&& rhs): 87 | m_Getter(rhs) 88 | {} 89 | lazy(lazy&&) = default; 90 | lazy& operator=(const lazy&) = default; 91 | lazy& operator=(lazy&&) = default; 92 | lazy& operator=(std::function&& rhs) 93 | { 94 | m_Value = boost::optional(); 95 | m_Getter = rhs; 96 | return *this; 97 | } 98 | 99 | //lazy(reactive& r) { 100 | // m_Getter = [&r]() -> T { 101 | // return r.get(); 102 | // }; 103 | // m_ChangeConnection = r.on_change.connect([this](const T& val){ 104 | // pend(); 105 | // }); 106 | // m_DestroyConnection = r.on_destroy.connect([this]{ 107 | // m_Getter = std::function(); 108 | // }); 109 | //} 110 | 111 | void set(const T& value){ 112 | m_Value = value; 113 | } 114 | 115 | void pend() const { 116 | m_Value = boost::optional(); 117 | } 118 | 119 | void recache() const { 120 | m_Value = m_Getter(); 121 | } 122 | 123 | void ensure() const { 124 | if(!m_Value) 125 | m_Value = m_Getter(); 126 | } 127 | 128 | bool valid() const { 129 | return bool(m_Value); 130 | } 131 | 132 | boost::optional try_get() const { 133 | return m_Value; 134 | } 135 | T& get() const { 136 | ensure(); 137 | return *m_Value; 138 | } 139 | T&& move() { 140 | ensure(); 141 | return std::move(*m_Value); 142 | } 143 | 144 | 145 | void getter(std::function func){ 146 | m_Getter = func; 147 | } 148 | 149 | T& operator()() const { 150 | return get(); 151 | } 152 | 153 | private: 154 | mutable boost::optional m_Value; 155 | std::function m_Getter; 156 | }; 157 | 158 | } 159 | 160 | #define KIT_REACTIVE_SIGNAL(FUNC, MEMBER) \ 161 | template \ 162 | boost::signals2::connection FUNC(Func&& func){ \ 163 | return MEMBER.on_change.connect(func); \ 164 | } 165 | 166 | #endif 167 | 168 | -------------------------------------------------------------------------------- /kit/reactive/signal.h: -------------------------------------------------------------------------------- 1 | #ifndef SIGNAL_H_GQBDAQXV 2 | #define SIGNAL_H_GQBDAQXV 3 | 4 | #include 5 | #include 6 | #include "../kit.h" 7 | 8 | namespace kit 9 | { 10 | template 11 | class signal; 12 | 13 | // a single-threaded signal 14 | template 15 | class signal 16 | { 17 | public: 18 | 19 | // TODO: slot class with some sort of scope test function or maybe filter id? 20 | 21 | typedef std::function function_type; 22 | typedef std::vector> container_type; 23 | 24 | void connect(function_type func) { 25 | if(not m_Recursion) 26 | m_Slots.push_back(std::move(func)); 27 | else 28 | m_PendingOperations.push_back([this, func]{ 29 | m_Slots.push_back(std::move(func)); 30 | }); 31 | } 32 | 33 | operator bool() const { 34 | return not m_Slots.empty(); 35 | } 36 | 37 | void operator()(Args... args) const { 38 | bool recur = not (m_Recursion++); 39 | for(auto&& slot: m_Slots) { 40 | slot(args...); 41 | } 42 | --m_Recursion; 43 | if(recur) 44 | run_pending_ops(); 45 | } 46 | 47 | std::vector accumulate(Args... args) { 48 | if(m_Slots.empty()) 49 | return std::vector(); 50 | std::vector r; 51 | r.reserve(m_Slots.size()); 52 | for(auto&& slot: m_Slots) 53 | r.push_back(slot(args...)); 54 | return r; 55 | } 56 | 57 | void sync(const std::function& func){ 58 | if(not m_Recursion) 59 | func(); 60 | else 61 | m_PendingOperations.push_back(func); 62 | } 63 | unsigned recursion() const { 64 | return m_Recursion; 65 | } 66 | void clear() 67 | { 68 | if(m_Recursion) 69 | { 70 | m_PendingOperations.push_back([this]{ 71 | m_Slots.clear(); 72 | --m_BlockReenter; 73 | }); 74 | ++m_BlockReenter; 75 | } 76 | else 77 | m_Slots.clear(); 78 | } 79 | 80 | bool empty() const { 81 | return m_Slots.empty(); 82 | } 83 | size_t size() const { 84 | return m_Slots.size(); 85 | } 86 | 87 | typedef typename container_type::iterator iterator; 88 | typedef typename container_type::const_iterator const_iterator; 89 | const_iterator cbegin() const { return m_Slots.cbegin(); } 90 | const_iterator cend() const { return m_Slots.cend(); } 91 | iterator begin() const { return m_Slots.begin(); } 92 | iterator end() const { return m_Slots.end(); } 93 | iterator begin() { return m_Slots.begin(); } 94 | iterator end() { return m_Slots.end(); } 95 | 96 | private: 97 | 98 | void run_pending_ops() const 99 | { 100 | for(auto&& op: m_PendingOperations) 101 | op(); 102 | m_PendingOperations.clear(); 103 | } 104 | 105 | container_type m_Slots; 106 | unsigned m_BlockReenter = 0; 107 | 108 | mutable std::vector> m_PendingOperations; 109 | mutable unsigned m_Recursion = 0; 110 | }; 111 | 112 | } 113 | 114 | #endif 115 | 116 | -------------------------------------------------------------------------------- /premake5.lua: -------------------------------------------------------------------------------- 1 | workspace("kit") 2 | targetdir("bin") 3 | 4 | configurations {"Debug", "Release"} 5 | 6 | defines { 7 | "GLM_FORCE_CTOR_INIT", 8 | "GLM_FORCE_RADIANS", 9 | "GLM_ENABLE_EXPERIMENTAL", 10 | "DO_NOT_USE_WMAIN", 11 | "NOMINMAX" 12 | } 13 | 14 | -- Debug Config 15 | configuration "Debug" 16 | defines { "DEBUG" } 17 | symbols "On" 18 | linkoptions { } 19 | 20 | configuration "linux" 21 | links { 22 | "z", 23 | "bfd", 24 | "iberty" 25 | } 26 | 27 | -- Release Config 28 | configuration "Release" 29 | defines { "NDEBUG" } 30 | optimize "speed" 31 | targetname("kit_dist") 32 | 33 | -- gmake Config 34 | configuration "gmake" 35 | buildoptions { "-std=c++11" } 36 | -- buildoptions { "-std=c++11", "-pedantic", "-Wall", "-Wextra", '-v', '-fsyntax-only'} 37 | links { 38 | "pthread", 39 | "SDL2", 40 | "boost_system", 41 | "boost_filesystem", 42 | "boost_coroutine", 43 | "boost_python", 44 | "boost_regex", 45 | "boost_chrono", 46 | "jsoncpp", 47 | } 48 | includedirs { 49 | "/usr/local/include/", 50 | "/usr/include/rapidxml/", 51 | "/usr/include/raknet/DependentExtensions" 52 | } 53 | 54 | libdirs { 55 | "/usr/local/lib" 56 | } 57 | 58 | buildoptions { 59 | "`python2-config --includes`", 60 | "`pkg-config --cflags cairomm-1.0 pangomm-1.4`" 61 | } 62 | 63 | linkoptions { 64 | "`python2-config --libs`", 65 | "`pkg-config --libs cairomm-1.0 pangomm-1.4`" 66 | } 67 | 68 | configuration "macosx" 69 | links { 70 | "boost_thread-mt", 71 | } 72 | 73 | --buildoptions { "-U__STRICT_ANSI__", "-stdlib=libc++" } 74 | --linkoptions { "-stdlib=libc++" } 75 | 76 | configuration "linux" 77 | links { 78 | --"GL", 79 | "boost_thread", 80 | } 81 | 82 | configuration "windows" 83 | toolset "v140" 84 | flags { "MultiProcessorCompile" } 85 | 86 | links { 87 | "ws2_32", 88 | "SDL2", 89 | "boost_system-vc140-mt-1_61", 90 | "boost_thread-vc140-mt-1_61", 91 | "boost_python-vc140-mt-1_61", 92 | "boost_coroutine-vc140-mt-1_61", 93 | "boost_regex-vc140-mt-1_61", 94 | "lib_json", 95 | } 96 | 97 | includedirs { 98 | "c:/local/boost_1_61_0", 99 | "c:/msvc/include", 100 | } 101 | configuration { "windows", "Debug" } 102 | libdirs { 103 | "c:/msvc/lib32/debug" 104 | } 105 | configuration { "windows" } 106 | libdirs { 107 | "c:/msvc/lib32", 108 | "c:/local/boost_1_61_0/lib32-msvc-14.0", 109 | } 110 | -- buildoptions { 111 | -- "/MP", 112 | -- "/Gm-", 113 | -- } 114 | 115 | configuration { "windows", "Debug" } 116 | links { 117 | "libboost_filesystem-vc140-mt-gd-1_61", 118 | } 119 | configuration {} 120 | configuration { "windows", "Release" } 121 | links { 122 | "libboost_filesystem-vc140-mt-1_61", 123 | } 124 | 125 | project "kit" 126 | kind "ConsoleApp" 127 | language "C++" 128 | 129 | -- Project Files 130 | files { 131 | "tests/**", 132 | "kit/**" 133 | } 134 | 135 | -- Exluding Files 136 | excludes { 137 | } 138 | 139 | includedirs { 140 | "lib/local_shared_ptr", 141 | } 142 | 143 | -------------------------------------------------------------------------------- /sg.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "env": { 4 | "CXX": "clang++" 5 | }, 6 | "makefile_params": [ 7 | "CXX='clang++'" 8 | ] 9 | }, 10 | 11 | "dependencies": { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/animation.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../kit/math/common.h" 4 | #include "../kit/log/log.h" 5 | #include 6 | #include "../kit/freq/animation.h" 7 | using namespace std; 8 | 9 | TEST_CASE("Animation","[animation]") { 10 | 11 | SECTION("frames and callbacks") { 12 | 13 | // keep track of callbacks triggered 14 | int callbacks = 0; 15 | auto callback_counter = [&callbacks] { 16 | ++callbacks; 17 | }; 18 | //Freq::Timeline timeline; 19 | Animation anim; 20 | //anim.timeline(&timeline); 21 | REQUIRE(floatcmp(anim.get(), 0.0f)); 22 | 23 | // add callback before adding to anim 24 | auto frame = Frame( 25 | 1.0f, 26 | Freq::Time::seconds(1), 27 | INTERPOLATE(linear) 28 | ); 29 | frame.callback(callback_counter); 30 | anim.frame(frame); 31 | 32 | REQUIRE(anim.size() == 1); 33 | 34 | anim.frame(Frame( 35 | 10.0f, 36 | Freq::Time::seconds(1), 37 | INTERPOLATE(linear) 38 | )); 39 | 40 | // add callback through anim's current frame ptr 41 | REQUIRE(anim.first_frame()); 42 | REQUIRE(anim.last_frame()); 43 | anim.last_frame()->callback(callback_counter); 44 | 45 | REQUIRE(anim.size() == 2); 46 | REQUIRE(floatcmp(anim.get(), 0.0f)); 47 | anim.logic(Freq::Time::seconds(1)); 48 | REQUIRE(!anim.elapsed()); 49 | REQUIRE(anim.size() == 1); 50 | REQUIRE(callbacks == 1); // only 1 callback should be triggered at this point 51 | REQUIRE(floatcmp(anim.get(), 1.0f)); 52 | anim.logic(Freq::Time::ms(500)); 53 | REQUIRE(floatcmp(anim.get(), 5.5f)); 54 | anim.logic(Freq::Time::ms(500)); 55 | REQUIRE(floatcmp(anim.get(), 10.0f)); 56 | REQUIRE(anim.elapsed()); 57 | 58 | // check that callback was triggered twice, one for each frame completion 59 | REQUIRE(callbacks == 2); 60 | } 61 | 62 | SECTION("interpolation") 63 | { 64 | float start, end, x; 65 | 66 | start = Interpolation::in_sine(0.0f, 1.0f, 0.0f); 67 | REQUIRE(floatcmp(start, 0.0f)); 68 | end = Interpolation::in_sine(0.0f, 1.0f, 1.0f); 69 | REQUIRE(floatcmp(end, 1.0f)); 70 | 71 | start = Interpolation::out_sine(0.0f, 1.0f, 0.0f); 72 | REQUIRE(floatcmp(start, 0.0f)); 73 | end = Interpolation::out_sine(0.0f, 1.0f, 1.0f); 74 | REQUIRE(floatcmp(end, 1.0f)); 75 | 76 | x = Interpolation::in_sine(0.0f, 1.0f, 0.5f); 77 | REQUIRE(x > 0.29f); 78 | REQUIRE(x < 3.00f); 79 | 80 | x = Interpolation::out_sine(0.0f, 1.0f, 0.5f); 81 | REQUIRE(x > 0.70f); 82 | REQUIRE(x < 0.71f); 83 | } 84 | 85 | SECTION("callbacks") 86 | { 87 | bool callback = false; 88 | Animation anim; 89 | auto restart = [&anim, &callback]{ 90 | callback = false; 91 | anim.frame(Frame( 92 | 1.0f, 93 | Freq::Time::seconds(1), 94 | INTERPOLATE(linear), 95 | [&callback]{ 96 | callback = true; 97 | } 98 | )); 99 | }; 100 | 101 | restart(); 102 | REQUIRE(not callback); 103 | anim.finish(); 104 | REQUIRE(callback); 105 | 106 | restart(); 107 | REQUIRE(not callback); 108 | anim.stop(); 109 | REQUIRE(not callback); 110 | 111 | restart(); 112 | REQUIRE(not callback); 113 | anim.abort(); 114 | REQUIRE(not callback); 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /tests/args.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../kit/args/args.h" 3 | #include "../kit/log/log.h" 4 | using namespace std; 5 | 6 | TEST_CASE("Args","[args]") { 7 | 8 | SECTION("empty") { 9 | Args args; 10 | REQUIRE(args.empty()); 11 | REQUIRE(args.size() == 0); 12 | } 13 | 14 | SECTION("at") { 15 | Args args; 16 | 17 | args = Args(vector{"a","b","c"}); 18 | 19 | REQUIRE(args.at(0) == "a"); 20 | REQUIRE(args.at(1) == "b"); 21 | REQUIRE(args.at(2) == "c"); 22 | REQUIRE(args.at(3) == ""); 23 | REQUIRE(args.at(-1) == "c"); 24 | REQUIRE(args.at(-2) == "b"); 25 | REQUIRE(args.at(-3) == "a"); 26 | REQUIRE(args.at(-4) == ""); 27 | } 28 | 29 | SECTION("has") { 30 | // empty 31 | Args args; 32 | REQUIRE(not args.has("foo")); 33 | REQUIRE(not args.has("-f")); 34 | REQUIRE(not args.has("--foobar")); 35 | 36 | // single arg 37 | args = Args(vector{"--foobar"}); 38 | REQUIRE(args.has("--foobar")); 39 | REQUIRE(not args.has("--foo")); 40 | 41 | // multiple args 42 | args = Args(vector{"--foo", "--bar"}); 43 | REQUIRE(args.has("--foo")); 44 | REQUIRE(args.has("--bar")); 45 | REQUIRE(not args.has("--baz")); 46 | 47 | // switches 48 | args = Args(); 49 | REQUIRE(not args.has('v', "verbose")); 50 | args = Args(vector{"--verbose"}); 51 | REQUIRE(args.has('v', "verbose")); // full word 52 | REQUIRE(not args.has('n', "nope")); 53 | args = Args(vector{"-v"}); 54 | REQUIRE(args.has('v', "verbose")); // single char 55 | REQUIRE(not args.has('n', "nope")); 56 | 57 | // multiple char switches (not combined) 58 | //args = Args(vector{"-abc"}, "-a -b -c"); 59 | //REQUIRE(args.has('a', "achar")); 60 | //REQUIRE(args.has('b', "bchar")); 61 | //REQUIRE(args.has('c', "cchar")); 62 | //args = Args(vector{"-ac"}, "-a -b -c"); 63 | //REQUIRE(args.has('a', "achar")); 64 | //REQUIRE(not args.has('b', "bchar")); 65 | //REQUIRE(args.has('c', "cchar")); 66 | } 67 | 68 | SECTION("option") { 69 | Args args; 70 | args = Args(vector{"-abc", "-d", "--go"}); 71 | REQUIRE(args.has('a')); 72 | REQUIRE(args.has("-a")); 73 | REQUIRE(args.has('b', "berry")); 74 | REQUIRE(args.has('c')); 75 | REQUIRE(args.has('d', "door")); 76 | REQUIRE(not args.has('e')); 77 | REQUIRE(not args.has('f', "foo")); 78 | REQUIRE(args.has('g', "go")); 79 | } 80 | 81 | SECTION("any") { 82 | Args args; 83 | args = Args(vector{"--foo", "--bar"}); 84 | REQUIRE(not args.any({"--bin"})); 85 | REQUIRE(not args.any({"--bin","--baz"})); 86 | REQUIRE(args.any({"--bar","--bin"})); 87 | REQUIRE(args.any({"--bin","--bar"})); 88 | REQUIRE(args.any({"--foo","--bar"})); 89 | } 90 | 91 | SECTION("key-value") { 92 | Args args; 93 | REQUIRE_NOTHROW(args = Args(vector{"--foo=bar"})); 94 | REQUIRE(args.value("foo") == "bar"); 95 | REQUIRE(args.value_or("foo","baz") == "bar"); 96 | REQUIRE(args.value_or("bin","baz") == "baz"); 97 | } 98 | 99 | SECTION("expected") { 100 | Args args; 101 | 102 | REQUIRE_NOTHROW(args = Args(vector{"--foo"}, "-f --foo")); 103 | REQUIRE(args.has('f',"foo")); 104 | REQUIRE(args.has("--foo")); 105 | REQUIRE(not args.has("-f")); 106 | 107 | REQUIRE_NOTHROW(args = Args(vector{"-f"}, "-f --foo")); 108 | REQUIRE(args.has('f',"foo")); 109 | REQUIRE(not args.has("--foo")); 110 | REQUIRE(args.has("-f")); 111 | 112 | { 113 | Log::Silencer ls; 114 | REQUIRE_THROWS(args = Args(vector{"--invalid"}, "-f --foo")); 115 | } 116 | 117 | REQUIRE_NOTHROW(Args(vector{"-ab"}, "-a -b")); 118 | { 119 | Log::Silencer ls; 120 | // -c invalid 121 | REQUIRE_THROWS(Args(vector{"-abc"}, "-a -b")); 122 | } 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /tests/async.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../kit/kit.h" 3 | #include "../kit/async/async.h" 4 | #include "../kit/async/async_fstream.h" 5 | //#include "../include/kit/async/task.h" 6 | //#include "../include/kit/async/channel.h" 7 | //#include "../include/kit/async/multiplexer.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | using namespace std; 13 | 14 | #define LOCK_WAIT_MS 10 15 | 16 | TEST_CASE("Task","[task]") { 17 | SECTION("empty task"){ 18 | Task task([]{ 19 | // empty 20 | }); 21 | auto fut = task.get_future(); 22 | task(); 23 | REQUIRE(kit::ready(fut)); 24 | } 25 | SECTION("retrying tasks"){ 26 | Task task([](bool err){ 27 | // in non-coroutine contexts (task() instead of coro), 28 | // YIELD() recalls the function 29 | if(err) 30 | YIELD(); 31 | }); 32 | auto fut = task.get_future(); 33 | 34 | REQUIRE_THROWS(task(true)); 35 | REQUIRE(fut.wait_for(std::chrono::seconds(0)) == 36 | std::future_status::timeout); 37 | 38 | REQUIRE_NOTHROW(task(false)); 39 | REQUIRE(kit::ready(fut)); 40 | } 41 | } 42 | 43 | TEST_CASE("Channel","[channel]") { 44 | SECTION("basic usage"){ 45 | Channel chan; 46 | REQUIRE(chan.size() == 0); 47 | while(true){ 48 | try{ 49 | chan << 42; 50 | break; 51 | }catch(const kit::yield_exception& rt){} 52 | }; 53 | REQUIRE(chan.size() == 1); 54 | int num = 0; 55 | while(true){ 56 | try{ 57 | chan >> num; 58 | break; 59 | }catch(const kit::yield_exception& rt){} 60 | }; 61 | REQUIRE(num == 42); 62 | } 63 | SECTION("peeking, coroutines, explicit locking"){ 64 | Multiplexer mx; 65 | auto chan = make_shared>(); 66 | auto cl = chan->lock(); 67 | atomic started = ATOMIC_VAR_INIT(false); 68 | mx[0].coro([&mx, chan, &started]{ 69 | int x; 70 | for(int i=1;i<=3;++i) 71 | { 72 | AWAIT_MX(mx, *chan << i); 73 | } 74 | started = true; 75 | for(int i=1;i<=3;++i) 76 | { 77 | //int n; 78 | //n = AWAIT_MX(mx, chan->peek()); 79 | //REQUIRE(n == i); 80 | AWAIT_MX(mx, *chan >> x); 81 | //REQUIRE(x == i); 82 | } 83 | }); 84 | REQUIRE(not started); 85 | boost::this_thread::sleep_for(boost::chrono::milliseconds(LOCK_WAIT_MS)); 86 | REQUIRE(not started); // should still be awaiting lock 87 | 88 | // do something else in same channel, to prove coroutine is paused 89 | mx[0].task([]{}).get(); 90 | 91 | cl.unlock(); // give up our lock, so coroutine can resume 92 | while(not started) { 93 | boost::this_thread::sleep_for(boost::chrono::milliseconds(1)); 94 | } 95 | REQUIRE(started); // intention of loop above 96 | mx.finish(); 97 | REQUIRE(started); 98 | } 99 | SECTION("retrying"){ 100 | Multiplexer mx; 101 | Channel chan; 102 | 103 | int sum = 0; 104 | mx[0].buffer(256); 105 | mx[0].task([&chan]{ 106 | // only way to stop this event is closing the channel from the outside 107 | 108 | chan << 1; // retries task if this blocks 109 | 110 | YIELD(); // keep this event going until chan is closed 111 | }); 112 | mx[1].task([&sum, &chan]{ 113 | int num; 114 | chan >> num; // if this blocks, task is retried later 115 | sum += num; 116 | if(sum < 5) 117 | YIELD(); 118 | chan.close(); // done, triggers the other circuit to throw 119 | }); 120 | mx.finish(); 121 | REQUIRE(sum == 5); 122 | } 123 | SECTION("nested tasks"){ 124 | Multiplexer mx; 125 | 126 | bool done = false; 127 | auto chan = make_shared>(); 128 | mx[0].task([&mx, chan, &done]{ 129 | auto ping = mx[0].task([chan]{ 130 | *chan << "ping"; 131 | }); 132 | auto pong = mx[0].task([chan]{ 133 | auto r = chan->get(); 134 | r[1] = 'o'; 135 | return r; 136 | }); 137 | mx[0].when(pong,[&done](future& pong){ 138 | auto str = pong.get(); 139 | done = (str == "pong"); 140 | }); 141 | }); 142 | mx.finish(); 143 | REQUIRE(done); 144 | } 145 | SECTION("get_until") { 146 | Multiplexer mx; 147 | std::string msg = "hello world"; 148 | size_t idx = 0; 149 | auto chan = make_shared>(); 150 | mx[0].task([chan, &idx, &msg]{ 151 | try{ 152 | *chan << msg.at(idx); 153 | ++idx; 154 | }catch(const std::out_of_range&){ 155 | return; 156 | } 157 | YIELD(); 158 | }); 159 | auto result = mx[1].task([chan]{ 160 | return chan->get_until(' '); 161 | }); 162 | mx.finish(); 163 | REQUIRE(result.get() == "hello"); 164 | } 165 | SECTION("buffered streaming") { 166 | Multiplexer mx; 167 | std::string in = "12345"; 168 | std::string out; 169 | //vector in = {'1','2','3','4','5'}; 170 | //vector out; 171 | auto chan = make_shared>(); 172 | chan->buffer(3); 173 | { 174 | mx[0].task([chan, &in]{ 175 | try{ 176 | // usually this will continue after first chunk 177 | // but let's stop it early 178 | //*chan << in; 179 | chan->stream(in); 180 | }catch(const kit::yield_exception&){ 181 | if(in.size() == 2) // repeat until 2 chars left 182 | return; 183 | } 184 | YIELD(); 185 | }); 186 | mx[1].task([chan, &out]{ 187 | //*chan >> out; 188 | chan->get(out); 189 | if(out.size() == 3) // repeat until obtaining chars 190 | return; 191 | YIELD(); 192 | }); 193 | } 194 | mx.finish(); 195 | REQUIRE(in == "45"); 196 | REQUIRE(out == "123"); 197 | //REQUIRE((in == vector{'4','5'})); 198 | //REQUIRE((out == vector{'1','2','3'})); 199 | } 200 | } 201 | 202 | //TEST_CASE("TaskQueue","[taskqueue]") { 203 | 204 | // SECTION("basic task queue") { 205 | // TaskQueue tasks; 206 | // REQUIRE(!tasks); 207 | // REQUIRE(tasks.empty()); 208 | // tasks([]{}); 209 | // REQUIRE(!tasks.empty()); 210 | // } 211 | 212 | // SECTION("run_once w/ futures") { 213 | // TaskQueue tasks; 214 | // auto fut0 = tasks([]{ 215 | // return 0; 216 | // }); 217 | // auto fut1 = tasks([]{ 218 | // return 1; 219 | // }); 220 | 221 | // REQUIRE(tasks.size() == 2); 222 | // REQUIRE(tasks); 223 | 224 | // REQUIRE(fut0.wait_for(std::chrono::seconds(0)) == 225 | // std::future_status::timeout); 226 | 227 | // tasks.run_once(); 228 | 229 | // REQUIRE(fut0.get() == 0); 230 | // REQUIRE(fut1.wait_for(std::chrono::seconds(0)) == 231 | // std::future_status::timeout); 232 | // REQUIRE(!tasks.empty()); 233 | // REQUIRE(tasks.size() == 1); 234 | 235 | // tasks.run_once(); 236 | 237 | // REQUIRE(fut1.get() == 1); 238 | // REQUIRE(tasks.empty()); 239 | // REQUIRE(tasks.size() == 0); 240 | // } 241 | 242 | // SECTION("run_all"){ 243 | // TaskQueue tasks; 244 | 245 | // tasks([]{}); 246 | // tasks([]{}); 247 | // tasks([]{}); 248 | 249 | // REQUIRE(tasks.size() == 3); 250 | 251 | // tasks.run(); 252 | 253 | // REQUIRE(tasks.size() == 0); 254 | // } 255 | 256 | // SECTION("nested task enqueue"){ 257 | // TaskQueue tasks; 258 | // auto sum = make_shared(0); 259 | // tasks([&tasks, sum]{ 260 | // (*sum) += 1; 261 | // tasks([&tasks, sum]{ 262 | // (*sum) += 10; 263 | // }); 264 | // }); 265 | // tasks.run(); 266 | // REQUIRE(*sum == 11); 267 | // REQUIRE(sum.use_count() == 1); 268 | // } 269 | 270 | //} 271 | 272 | TEST_CASE("Multiplexer","[multiplexer]") { 273 | 274 | SECTION("thread wait on condition"){ 275 | Multiplexer mx; 276 | std::atomic num = ATOMIC_VAR_INIT(0); 277 | mx[0].task([&num]{ 278 | num = 42; 279 | }); 280 | mx[1].when( 281 | [&num]{return num == 42;}, 282 | [&num]{num = 100;} 283 | ); 284 | mx.finish(); 285 | //while(num != 100){} 286 | REQUIRE(num == 100); 287 | } 288 | SECTION("thread wait on future"){ 289 | Multiplexer mx; 290 | Task numbers([]{ 291 | return 42; 292 | }); 293 | auto fut = numbers.get_future(); 294 | bool done = false; 295 | //numbers.run(); 296 | //REQUIRE(fut.get() == 42); 297 | mx[0].when(fut, [&done](std::future& num){ 298 | //done = true; 299 | done = (num.get() == 42); 300 | }); 301 | numbers(); 302 | mx.finish(); 303 | REQUIRE(done); 304 | } 305 | } 306 | 307 | TEST_CASE("Coroutines","[coroutines]") { 308 | SECTION("Interleaved"){ 309 | // In most apps, we'd use the singleton multiplexer "MX" 310 | // and use AWAIT() instead of AWAIT_MX(mx, ...) 311 | // But since we want to isolate the multiplexer across unit tests 312 | // we will declare a separate one here 313 | Multiplexer mx; 314 | 315 | // create an integer channel 316 | auto chan = make_shared>(); 317 | 318 | // enforce context switching by assigning both tasks to same circuit 319 | // and only allowing 1 integer across the channel at once 320 | chan->buffer(1); 321 | const int C = 0; 322 | 323 | // schedule a coroutine to be our consumer 324 | auto nums_fut = mx[C].coro>([chan, &mx]{ 325 | vector nums; 326 | while(not chan->closed()) 327 | { 328 | // recv some numbers from our channel 329 | // AWAIT() allows context switching instead of blocking 330 | int n = AWAIT_MX(mx, chan->get()); 331 | nums.push_back(n); 332 | } 333 | return nums; 334 | }); 335 | // schedule a coroutine to be our producer of integers 336 | mx[C].coro([chan, &mx]{ 337 | // send some numbers through the channel 338 | // AWAIT() allows context switching instead of blocking 339 | AWAIT_MX(mx, *chan << 1); 340 | AWAIT_MX(mx, *chan << 2); 341 | AWAIT_MX(mx, *chan << 3); 342 | chan->close(); 343 | }); 344 | 345 | mx.finish(); 346 | 347 | // see if all the numbers got through the channel 348 | REQUIRE((nums_fut.get() == vector{1,2,3})); 349 | } 350 | SECTION("Exceptions and stack unwinding"){ 351 | Multiplexer mx; 352 | 353 | struct UnwindMe { 354 | bool* unwound; 355 | UnwindMe(bool* b): 356 | unwound(b) 357 | {} 358 | ~UnwindMe() { 359 | *unwound = true; 360 | } 361 | }; 362 | 363 | // TASK 364 | { 365 | auto unwound = kit::make_unique(false); 366 | bool* unwoundptr = unwound.get(); 367 | auto fut = mx[0].task([unwoundptr]{ 368 | UnwindMe uw(unwoundptr); 369 | throw kit::interrupt(); // example exception 370 | }); 371 | REQUIRE_THROWS(fut.get()); 372 | REQUIRE(*unwound); 373 | } 374 | // COROUTINE 375 | { 376 | auto unwound = kit::make_unique(false); 377 | bool* unwoundptr = unwound.get(); 378 | auto fut = mx[0].coro([unwoundptr]{ 379 | UnwindMe uw(unwoundptr); 380 | throw kit::interrupt(); // example exception 381 | }); 382 | REQUIRE_THROWS(fut.get()); 383 | REQUIRE(*unwound); 384 | } 385 | 386 | mx.finish(); 387 | } 388 | SECTION("Stopping empty"){ 389 | Multiplexer mx; 390 | mx.stop(); 391 | } 392 | SECTION("Finishing empty"){ 393 | Multiplexer mx; 394 | mx.finish(); 395 | } 396 | SECTION("Stopping tasks"){ 397 | Multiplexer mx; 398 | bool done = false; 399 | mx[0].task([&mx, &done]{ 400 | YIELD_MX(mx); // retries func when not in coro (see task above) 401 | done = true; 402 | }); 403 | boost::this_thread::sleep_for(boost::chrono::milliseconds(10)); 404 | mx.stop(); 405 | REQUIRE(done == false); 406 | } 407 | SECTION("Stopping coroutines"){ 408 | Multiplexer mx; 409 | bool done = false; 410 | std::atomic ready = ATOMIC_VAR_INIT(false); 411 | mx[0].coro([&mx, &ready, &done]{ 412 | try{ 413 | for(;;){ 414 | YIELD_MX(mx); 415 | ready = true; 416 | } 417 | }catch(...){ 418 | done = true; 419 | throw; 420 | } 421 | }); 422 | // wait for at least one yield to pass 423 | while(not ready){} 424 | // the coroutine should unwind, setting done to true 425 | mx.stop(); 426 | REQUIRE(done == true); 427 | } 428 | } 429 | 430 | TEST_CASE("async_wrap","[async_wrap]") { 431 | SECTION("basic usage"){ 432 | int num = 0; 433 | async_wrap val(42); 434 | REQUIRE(num != val.get()); 435 | val.with([&num](int& v){ 436 | num = v; 437 | }).get(); 438 | REQUIRE(num == 42); 439 | REQUIRE(num == val.get()); 440 | } 441 | } 442 | 443 | TEST_CASE("async_fstream","[async_fstream]") { 444 | SECTION("basic usage"){ 445 | Multiplexer mx; 446 | { 447 | const std::string fn = "test.txt"; 448 | const std::string not_fn = "test_nonexist.txt"; 449 | async_fstream file(&mx[0]); 450 | REQUIRE(not file.is_open().get()); 451 | file.open(fn).get(); 452 | REQUIRE(file.is_open().get()); 453 | REQUIRE(file.with([](fstream& f){ 454 | return f.is_open(); 455 | }).get() == true); 456 | REQUIRE(file.filename().get() == fn); 457 | //std::string buf = file.with([](const std::string& b){ 458 | // return b; 459 | //}).get(); 460 | REQUIRE(file.buffer().get() == "test\n"); // contents of file 461 | file.close().get(); 462 | REQUIRE(file.filename().get() == ""); 463 | 464 | // open behavior on non-existant file responds like fstream 465 | file.open(not_fn).get(); 466 | REQUIRE(not file.is_open().get()); 467 | 468 | // failed opens still store file name 469 | REQUIRE(not file.filename().get().empty()); 470 | } 471 | mx.finish(); 472 | } 473 | } 474 | 475 | TEST_CASE("Temp","[temp]") { 476 | SECTION("Some quick tests for debugging"){ 477 | Multiplexer mx; 478 | mx.finish(); 479 | } 480 | } 481 | 482 | -------------------------------------------------------------------------------- /tests/cache.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../kit/cache/cache.h" 3 | using namespace std; 4 | 5 | struct Object 6 | { 7 | Object(const std::tuple& args): 8 | m_Value(std::get<0>(args)) 9 | {} 10 | std::string value() const { 11 | return m_Value; 12 | } 13 | std::string m_Value; 14 | }; 15 | 16 | TEST_CASE("Cache","[cache]") { 17 | 18 | SECTION("basic usage") { 19 | Cache cache; 20 | cache.register_class(); 21 | cache.register_resolver( 22 | [](tuple) { 23 | return 0; 24 | } 25 | ); 26 | 27 | auto hi_ptr = cache.cache("hi"); // hold shared_ptr to resource 28 | REQUIRE(cache.size()==1); 29 | 30 | cache.cache("hi"); // reuse old resource 31 | REQUIRE(cache.size()==1); 32 | 33 | cache.cache("hello"); // new resource 34 | REQUIRE(cache.size()==2); 35 | REQUIRE(!cache.empty()); 36 | 37 | cache.optimize(); 38 | REQUIRE(hi_ptr->value() == "hi"); 39 | REQUIRE(cache.size()==1); 40 | 41 | hi_ptr = nullptr; 42 | REQUIRE(cache.size()==1); 43 | cache.optimize(); 44 | 45 | REQUIRE(cache.size()==0); 46 | REQUIRE(cache.empty()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/factory.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../kit/factory/factory.h" 3 | using namespace std; 4 | 5 | TEST_CASE("Factory","[factory]") { 6 | 7 | SECTION("basic usage") { 8 | Factory factory; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /tests/fs.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../kit/fs/fs.h" 4 | using namespace std; 5 | 6 | TEST_CASE("fs","[fs]") { 7 | SECTION("usage") { 8 | // TODO: write OS-specific tests 9 | 10 | // $HOME or $HOMEPATH 11 | cout << kit::homedir() << endl; 12 | 13 | // $XDG_CONFIG_HOME or $HOME/.config on Linux 14 | cout << kit::configdir() << endl; 15 | 16 | // $XDG_CONFIG_HOME/test or $HOME/.config/test on Linux 17 | cout << kit::configdir("test") << endl; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /tests/log.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../kit/log/log.h" 4 | using namespace std; 5 | 6 | TEST_CASE("Log","[log]") { 7 | SECTION("thread-safe indents") { 8 | REQUIRE(Log::get().indents() == 0); 9 | { 10 | Log::Indent a; 11 | REQUIRE(Log::get().indents() == 1); 12 | 13 | auto t = std::thread([]{ 14 | REQUIRE(Log::get().indents() == 0); 15 | REQUIRE(Log::get().num_threads() == 1); 16 | Log::get().push_indent(); 17 | REQUIRE(Log::get().num_threads() == 2); 18 | Log::get().push_indent(); 19 | REQUIRE(Log::get().indents() == 2); 20 | Log::get().pop_indent(); 21 | Log::get().pop_indent(); 22 | REQUIRE(Log::get().indents() == 0); 23 | }); 24 | 25 | REQUIRE(Log::get().indents() == 1); 26 | REQUIRE(Log::get().num_threads() == 1); 27 | t.join(); 28 | 29 | Log::Indent b; 30 | 31 | { 32 | Log::Indent c; 33 | REQUIRE(Log::get().indents() == 3); 34 | } 35 | 36 | REQUIRE(Log::get().indents() == 2); 37 | } 38 | REQUIRE(Log::get().indents() == 0); 39 | 40 | // make sure all indent counts for threads auto-cleared 41 | REQUIRE(Log::get().num_threads() == 0); 42 | } 43 | 44 | SECTION("emit"){ 45 | //auto ll = Log::get().lock(); 46 | //Log::Capturer c; 47 | //LOG("hello"); 48 | //REQUIRE(Log::get().emit() == "hello"); 49 | } 50 | SECTION("consume prefix"){ 51 | // purpose 52 | REQUIRE(Log::consume_prefix("(example.cpp:123): data") == "data"); 53 | 54 | // incomplete prefixes 55 | REQUIRE(Log::consume_prefix("(") == "("); 56 | REQUIRE(Log::consume_prefix("()") == "()"); 57 | REQUIRE(Log::consume_prefix("():") == "():"); 58 | REQUIRE(Log::consume_prefix("(blah):") == "(blah):"); 59 | REQUIRE(Log::consume_prefix("(blah:123):") == "(blah:123):"); 60 | 61 | // complex prefix 62 | REQUIRE(Log::consume_prefix("(): ") == ""); 63 | 64 | // nested colons are kept 65 | REQUIRE(Log::consume_prefix("(blah): ") == ""); 66 | REQUIRE(Log::consume_prefix("(blah:123): ") == ""); 67 | REQUIRE(Log::consume_prefix("(blah): data") == "data"); 68 | REQUIRE(Log::consume_prefix("(:::): data") == "data"); 69 | 70 | // multi-line 71 | REQUIRE(Log::consume_prefix("(): \n(): ") == "\n"); 72 | REQUIRE(Log::consume_prefix("(): \n\n(): ") == "\n\n"); 73 | REQUIRE(Log::consume_prefix("(): foo\n(): bar") == "foo\nbar"); 74 | REQUIRE(Log::consume_prefix("foo\n(): \n(): \nbar") == "foo\n\n\nbar"); 75 | } 76 | 77 | SECTION("signals"){ 78 | int happened = 0; 79 | { 80 | boost::signals2::scoped_connection connection = Log::get().on_log.connect([&](std::string, Log::Level lev){ 81 | ++happened; 82 | }); 83 | REQUIRE(happened == 0); 84 | LOG("test"); 85 | REQUIRE(happened == 1); 86 | } 87 | REQUIRE(happened == 1); 88 | LOG("test2"); 89 | REQUIRE(happened == 1); 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /tests/meta.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../kit/kit.h" 3 | #include "../kit/meta/meta.h" 4 | #include "../kit/math/common.h" 5 | #include 6 | using namespace std; 7 | using kit::make_local_shared; 8 | using kit::local_shared_ptr; 9 | 10 | TEST_CASE("Meta","[meta]") { 11 | 12 | SECTION("empty") { 13 | auto m = Meta::make(); 14 | REQUIRE(m); 15 | REQUIRE(!*m); 16 | REQUIRE(m->empty()); 17 | REQUIRE(m->size() == 0); 18 | } 19 | 20 | SECTION("set") { 21 | auto m = Meta::make(); 22 | m->set("one", 1); 23 | 24 | REQUIRE(*m); 25 | REQUIRE(!m->empty()); 26 | REQUIRE(m->size() == 1); 27 | 28 | // range checking 29 | REQUIRE_THROWS_AS(m->at(2), std::out_of_range); 30 | // out_of_range check happens before any_type checking 31 | REQUIRE_THROWS_AS(m->at(2), std::out_of_range); 32 | 33 | // type checking 34 | REQUIRE_NOTHROW(m->at(0)); 35 | REQUIRE_THROWS_AS(m->at(0), boost::bad_any_cast); 36 | REQUIRE_THROWS_AS(m->meta(0), boost::bad_any_cast); 37 | 38 | m->set("two", "2"); 39 | REQUIRE(m->size() == 2); 40 | REQUIRE(m->key_count() == 2); 41 | } 42 | 43 | SECTION("add") { 44 | auto m = Meta::make(); 45 | 46 | REQUIRE(m->add(Meta::make()) == 0); 47 | REQUIRE(m->add(Meta::make()) == 1); 48 | REQUIRE(m->size() == 2); 49 | REQUIRE(m->key_count() == 0); 50 | } 51 | 52 | SECTION("ensure") { 53 | auto m = Meta::make(); 54 | 55 | REQUIRE(m->ensure("a", 1) == 1); 56 | REQUIRE(m->ensure("b", 2) == 2); 57 | REQUIRE(m->ensure("a", 3) == 1); 58 | REQUIRE(m->at("a") == 1); 59 | REQUIRE(m->size() == 2); 60 | 61 | // TODO: type conflicts 62 | REQUIRE(m->ensure("a", "test") == "test"); 63 | } 64 | 65 | SECTION("remove") { 66 | 67 | SECTION("at index") { 68 | auto m = Meta::make(); 69 | m->add(1); 70 | m->add(2); // index 1 71 | m->add(3); 72 | REQUIRE(m->is_array()); 73 | m->remove(1); 74 | REQUIRE(m->is_array()); 75 | REQUIRE(m->at(0) == 1); 76 | REQUIRE(m->at(1) == 3); 77 | REQUIRE(m->size() == 2); 78 | REQUIRE_THROWS_AS(m->at(2), std::out_of_range); 79 | m->remove(0); 80 | REQUIRE(m); 81 | m->remove(0); 82 | REQUIRE(!*m); 83 | REQUIRE_THROWS_AS(m->remove(0), std::out_of_range); 84 | } 85 | 86 | SECTION("pop") { 87 | SECTION("front") { 88 | auto m = Meta::make(); 89 | m->add(1); 90 | m->add(2); 91 | m->pop_front(); 92 | REQUIRE(m->at(0) == 2); 93 | REQUIRE(m->size() == 1); 94 | m->pop_front(); 95 | REQUIRE(m->empty()); 96 | REQUIRE_THROWS_AS(m->pop_front(), std::out_of_range); 97 | } 98 | SECTION("back") { 99 | auto m = Meta::make(); 100 | m->add(1); 101 | m->add(2); 102 | m->pop_back(); 103 | REQUIRE(m->at(0) == 1); 104 | REQUIRE(m->size() == 1); 105 | m->pop_back(); 106 | REQUIRE(m->empty()); 107 | REQUIRE_THROWS_AS(m->pop_back(), std::out_of_range); 108 | } 109 | } 110 | 111 | SECTION("by key") { 112 | auto m = Meta::make(); 113 | m->set("one" ,1); 114 | m->set("two", 2); 115 | m->set("three", 3); 116 | REQUIRE(m->is_map()); 117 | m->remove("two"); 118 | REQUIRE(m->is_map()); 119 | REQUIRE_THROWS_AS(m->at("two"), std::out_of_range); 120 | REQUIRE(m->at(0) == 1); 121 | REQUIRE(m->at(1) == 3); 122 | REQUIRE(m->id("one") == 0); 123 | REQUIRE(m->id("three") == 1); 124 | m->remove("one"); 125 | REQUIRE(m->id("three") == 0); 126 | m->remove("three"); 127 | REQUIRE_THROWS_AS(m->remove("two"), std::out_of_range); 128 | REQUIRE_THROWS_AS(m->remove("four"), std::out_of_range); 129 | } 130 | } 131 | 132 | SECTION("types") { 133 | { 134 | auto m = Meta::make(); 135 | m->set("nullptr", nullptr); 136 | m->set("nullmeta", local_shared_ptr()); 137 | //REQUIRE(not m->at("nullptr")); 138 | //REQUIRE(not m->at>("nullmeta")); 139 | } 140 | } 141 | 142 | //SECTION("copying and assignment") { 143 | // // n/a 144 | //} 145 | 146 | SECTION("clear") { 147 | auto m = Meta::make(); 148 | m->set("one",1); 149 | m->set("two","2"); 150 | 151 | m->clear(); 152 | REQUIRE(m); 153 | REQUIRE(!*m); 154 | REQUIRE(m->empty()); 155 | REQUIRE(m->size() == 0); 156 | } 157 | 158 | SECTION("at") { 159 | auto m = Meta::make(); 160 | m->set("one", 1); 161 | m->set("two", "2"); 162 | m->add(Meta::make()); 163 | 164 | REQUIRE(m->at("one") == 1); 165 | REQUIRE(m->at(0) == 1); 166 | REQUIRE(m->at(1) == "2"); 167 | REQUIRE_THROWS_AS(m->at(3), std::out_of_range); 168 | REQUIRE_THROWS_AS(m->at("one"), boost::bad_any_cast); 169 | } 170 | 171 | SECTION("parent") { 172 | auto m = Meta::make(); 173 | REQUIRE(!m->parent()); 174 | REQUIRE(m->root() == m); 175 | auto c1 = Meta::make(); 176 | m->add(c1); 177 | REQUIRE(c1->parent() == m); 178 | } 179 | 180 | SECTION("iterate") { 181 | SECTION("map") { 182 | string json = "{\"one\":1,\"two\":2,\"three\": 3}"; 183 | auto m = Meta::make(MetaFormat::JSON, json); 184 | int i = 0; 185 | m->each_c([&]( 186 | local_shared_ptr parent, 187 | const MetaElement& e, 188 | unsigned level 189 | ){ 190 | REQUIRE(parent); 191 | REQUIRE(parent == m); 192 | //REQUIRE(level == 0); 193 | REQUIRE(!e.key.empty()); 194 | REQUIRE(json.find("\"" + e.key + "\"") != string::npos); 195 | 196 | // key order not required 197 | //REQUIRE(i == boost::any_cast(e.value)); 198 | 199 | ++i; 200 | return MetaLoop::STEP; 201 | }); 202 | REQUIRE(i == m->size()); 203 | } 204 | SECTION("array") { 205 | } 206 | SECTION("recursive") { 207 | } 208 | SECTION("by type") { 209 | } 210 | } 211 | 212 | SECTION("each") { 213 | } 214 | 215 | SECTION("merge") { 216 | } 217 | 218 | SECTION("serialization") { 219 | 220 | SECTION("objects") { 221 | auto m = Meta::make(MetaFormat::JSON,"{\"one\":1}"); 222 | REQUIRE(!m->empty()); 223 | REQUIRE(m->at("one") == 1); 224 | REQUIRE(!m->empty()); 225 | m->clear(); 226 | REQUIRE(m->empty()); 227 | 228 | m = Meta::make(); 229 | m->deserialize(MetaFormat::JSON,"{\"one\":1}"); 230 | REQUIRE(m->at("one") == 1); 231 | 232 | string data = m->serialize(MetaFormat::JSON); 233 | REQUIRE(!m->empty()); 234 | m->clear(); 235 | REQUIRE(m->empty()); 236 | m->deserialize(MetaFormat::JSON,data); 237 | REQUIRE(not m->empty()); 238 | REQUIRE(m->at("one") == 1); 239 | 240 | m->set("test", Meta::make()); 241 | m->meta("test")->set("two", 2); 242 | data = m->serialize(MetaFormat::INI); 243 | m->clear(); 244 | REQUIRE(m->empty()); 245 | m->deserialize(MetaFormat::INI, data); 246 | REQUIRE(not m->empty()); 247 | REQUIRE(m->at("one") == 1); 248 | REQUIRE(m->meta("test")->at("two") == 2); 249 | 250 | m->clear(); 251 | m->deserialize(MetaFormat::INI, 252 | "[test]\none=1\nyes=true" 253 | ); 254 | REQUIRE(m->meta("test")->at("yes")); 255 | CHECK(boost::trim_copy(m->serialize(MetaFormat::INI))=="[test]\none=1\nyes=true"); 256 | } 257 | 258 | SECTION("arrays") { 259 | auto m = Meta::make(MetaFormat::JSON,"{\"numbers\":[1,2,3]}"); 260 | auto a = m->at>(0); 261 | REQUIRE(a); 262 | REQUIRE(a->size() == 3); 263 | REQUIRE(a->at(0) == 1); 264 | REQUIRE(a->at(1) == 2); 265 | REQUIRE(a->at(2) == 3); 266 | 267 | // strings 268 | m = Meta::make(MetaFormat::JSON,"{ \"a\": [\"b\",\"c\"] }"); 269 | a = m->at>(0); 270 | REQUIRE(a); 271 | REQUIRE(a->size() == 2); 272 | REQUIRE(a->at(0) == "b"); 273 | REQUIRE(a->at(1) == "c"); 274 | 275 | // nested 276 | m = Meta::make(MetaFormat::JSON,"{\"numbers\":[[],[[]],[[],[]]]}"); 277 | a = m->at>(0); 278 | REQUIRE(a); 279 | REQUIRE(a->size() == 3); 280 | REQUIRE(a->meta(0)->size() == 0); 281 | REQUIRE(a->meta(1)->size() == 1); 282 | REQUIRE(a->meta(1)->meta(0)->size() == 0); 283 | REQUIRE_THROWS_AS(a->meta(1)->meta(1), std::out_of_range); 284 | REQUIRE(a->meta(2)->size() == 2); 285 | REQUIRE(a->meta(2)->meta(0)->size() == 0); 286 | REQUIRE(a->meta(2)->meta(1)->size() == 0); 287 | REQUIRE_THROWS_AS(a->meta(2)->meta(2), std::out_of_range); 288 | } 289 | 290 | SECTION("deserialization flags"){ 291 | 292 | { 293 | // no deserialize flags 294 | auto m = Meta::make(MetaFormat::JSON,"{\"one\":1}"); 295 | m->deserialize(MetaFormat::JSON,"{\"two\":2}"); 296 | REQUIRE(m->size() == 1); // no flags should clear prior data 297 | } 298 | 299 | { 300 | // same as above, but with F_MERGE flag 301 | auto m = Meta::make(MetaFormat::JSON,"{\"one\":1}"); 302 | m->deserialize(MetaFormat::JSON,"{\"two\":2}", 303 | Meta::F_MERGE // <-- new flag 304 | ); 305 | REQUIRE(m->size() == 2); // old data is preserved 306 | } 307 | } 308 | 309 | SECTION("nested") { 310 | 311 | } 312 | 313 | SECTION("correct types") { 314 | // make sure doubles with trailing .0 don't serialize as ints 315 | auto m = Meta::make(MetaFormat::JSON,"{\"one\":1.0}"); 316 | REQUIRE_THROWS_AS(m->at("one"), boost::bad_any_cast); 317 | REQUIRE(floatcmp(m->at("one"), 1.0)); 318 | } 319 | } 320 | } 321 | 322 | -------------------------------------------------------------------------------- /tests/mutex_wrap.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../kit/kit.h" 3 | using namespace std; 4 | using namespace kit; 5 | 6 | TEST_CASE("Mutex wrap","[mutex_wrap]") { 7 | 8 | SECTION("basic usage") { 9 | 10 | mutex_wrap num(0); 11 | REQUIRE(num == 0); 12 | num.with([](int& num){ 13 | num = 1; 14 | }); 15 | REQUIRE(num == 1); 16 | 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /tests/net.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../kit/net/net.h" 3 | #include 4 | using namespace std; 5 | 6 | TEST_CASE("Socket","[socket]") { 7 | SECTION("tcp server"){ 8 | // TODO: initiate mock client with netcat 9 | // warn and stop if netcat does not exist 10 | 11 | //TCPSocket server; 12 | 13 | //REQUIRE(not server); 14 | //REQUIRE_NOTHROW(server.open()); 15 | 16 | //REQUIRE_NOTHROW(server.bind(1337)); 17 | //REQUIRE_NOTHROW(server.listen(10)); 18 | 19 | //TCPSocket client; 20 | ////for(;;) 21 | ////{ 22 | //// try{ 23 | // client = server.accept(); 24 | //// }catch(...){ 25 | //// continue; 26 | //// } 27 | //// boost::this_thread::yield(); 28 | ////} 29 | //REQUIRE(client); 30 | 31 | //client.send("hello world!"); 32 | //const int SZ = 128; 33 | //uint8_t buf[SZ]; 34 | //int sz = SZ; 35 | //while(not client.select()) { 36 | // boost::this_thread::yield(); 37 | //} 38 | //REQUIRE(client.select()); 39 | //std::string msg; 40 | //REQUIRE_NOTHROW(msg = client.recv()); 41 | //REQUIRE(msg == "hello world!"); 42 | 43 | //REQUIRE_NOTHROW(client.close()); 44 | //REQUIRE(not client); 45 | //REQUIRE_NOTHROW(server.close()); 46 | //REQUIRE(not server); 47 | 48 | // TODO: kill mock client 49 | } 50 | SECTION("tcp client"){ 51 | // TODO: initiate mock server process 52 | //TCPSocket s; 53 | } 54 | SECTION("udp server"){ 55 | //UDPSocket s; 56 | //REQUIRE(not s); 57 | //REQUIRE_NOTHROW(s.open()); 58 | //REQUIRE(s); 59 | ////REQUIRE(not s.select()); 60 | //REQUIRE_NOTHROW(s.close()); 61 | //REQUIRE(not s); 62 | } 63 | SECTION("udp client"){ 64 | } 65 | SECTION("addresses"){ 66 | Address addr; 67 | 68 | addr = Address("1.2.3.4:5"); 69 | REQUIRE(addr.ip() == "1.2.3.4"); 70 | REQUIRE(addr.port() == 5); 71 | REQUIRE(string(addr) == "1.2.3.4:5"); 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /tests/reactive.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../kit/reactive/reactive.h" 3 | #include "../kit/reactive/signal.h" 4 | using namespace std; 5 | using namespace kit; 6 | 7 | TEST_CASE("Signal","[signal]") { 8 | SECTION("basic") { 9 | unsigned i = 0; 10 | signal sig; 11 | REQUIRE(sig == false); 12 | sig.connect([&i]{++i;}); 13 | REQUIRE(sig == true); 14 | REQUIRE(i == 0); 15 | sig(); 16 | REQUIRE(i == 1); 17 | sig(); 18 | REQUIRE(i == 2); 19 | REQUIRE(sig == true); 20 | sig.clear(); 21 | REQUIRE(sig == false); 22 | sig(); 23 | REQUIRE(i == 2); 24 | } 25 | SECTION("accumulation") { 26 | signal sig; 27 | sig.connect([]{ return 1; }); 28 | sig.connect([]{ return 2; }); 29 | sig.connect([]{ return 3; }); 30 | auto vals = sig.accumulate(); 31 | REQUIRE((vals == vector{1,2,3})); 32 | } 33 | SECTION("reentrant") { 34 | signal sig; 35 | unsigned call_count = 0; 36 | sig.connect([&sig, &call_count]{ 37 | sig.connect([&sig, &call_count]{ 38 | ++call_count; 39 | }); 40 | }); 41 | REQUIRE(call_count == 0); 42 | sig(); 43 | REQUIRE(call_count == 0); 44 | sig(); 45 | REQUIRE(call_count == 1); 46 | } 47 | //SECTION("sync & pend") { 48 | //} 49 | } 50 | 51 | TEST_CASE("Reactive","[reactive]") { 52 | 53 | SECTION("as a value") { 54 | 55 | reactive num; 56 | REQUIRE(num.get() == *num); // default init 57 | REQUIRE(num.get() == 0); // default init 58 | 59 | // set from integer rvalue 60 | num = 1; 61 | REQUIRE(num.get() == 1); 62 | 63 | int temp = 2; 64 | num = temp; 65 | REQUIRE(num.get() == 2); 66 | 67 | // copy from another reactive (implicitly deleted) 68 | //num = reactive(3); 69 | //REQUIRE(num.get() == 3); 70 | } 71 | 72 | SECTION("as a signal") { 73 | bool updated = false; 74 | //bool destroyed = false; 75 | 76 | reactive prop; 77 | prop.on_change.connect([&updated](const bool& b){ 78 | updated = b; 79 | }); 80 | //prop.on_destroy.connect([&destroyed](){ 81 | // destroyed = true; 82 | //}); 83 | REQUIRE(updated == false); // still false 84 | prop = true; 85 | REQUIRE(updated == true); // now true 86 | 87 | //REQUIRE(destroyed == false); 88 | //prop.clear(); 89 | //REQUIRE(destroyed == true); 90 | } 91 | 92 | } 93 | 94 | TEST_CASE("Lazy","[lazy]") { 95 | 96 | SECTION("as a value") { 97 | unsigned call_count = 0; 98 | lazy num([&call_count]{ 99 | ++call_count; 100 | return 1+1; 101 | }); 102 | REQUIRE(not num.valid()); 103 | REQUIRE(call_count == 0); 104 | REQUIRE(num.get() == 2); 105 | REQUIRE(call_count == 1); 106 | REQUIRE(num.valid()); 107 | REQUIRE(num.get() == 2); 108 | REQUIRE(call_count == 1); // still one 109 | 110 | REQUIRE(num.valid()); 111 | num.pend(); 112 | REQUIRE(not num.valid()); 113 | REQUIRE(not num.try_get()); 114 | 115 | num.ensure(); 116 | REQUIRE(num.valid()); 117 | REQUIRE(num.try_get()); 118 | REQUIRE(*num.try_get() == 2); 119 | 120 | num.set(3); 121 | REQUIRE(*num.try_get() == 3); 122 | REQUIRE(num.get() == 3); 123 | 124 | num.recache(); 125 | REQUIRE(*num.try_get() == 2); 126 | REQUIRE(num.get() == 2); 127 | } 128 | } 129 | 130 | -------------------------------------------------------------------------------- /tests/schema.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../kit/meta/schema.h" 5 | using namespace std; 6 | using kit::make_local_shared; 7 | using kit::local_shared_ptr; 8 | 9 | TEST_CASE("Schema","[schema]") { 10 | 11 | SECTION("load from string") { 12 | 13 | auto schema = make_local_shared( 14 | make_local_shared( 15 | MetaFormat::JSON, 16 | R"({})" 17 | ) 18 | ); 19 | REQUIRE(schema->meta()); 20 | REQUIRE(schema->meta().get()); 21 | REQUIRE(schema->meta()->empty()); 22 | 23 | schema = make_local_shared( 24 | make_local_shared( 25 | MetaFormat::JSON, 26 | R"({ 27 | "foo": "bar", 28 | "a": [ 29 | "a", 30 | "b", 31 | "c" 32 | ], 33 | "b": { 34 | "a": 1, 35 | "b": 2, 36 | "b": 3 37 | } 38 | })" 39 | ) 40 | ); 41 | REQUIRE(schema->meta()->size() == 3); 42 | REQUIRE(schema->meta()->at("foo") == "bar"); 43 | } 44 | 45 | SECTION("validate") { 46 | 47 | auto schema = make_local_shared( 48 | make_local_shared( 49 | MetaFormat::JSON, 50 | R"({ 51 | "foo": { 52 | ".values": [ 53 | "a", 54 | "b", 55 | "c" 56 | ] 57 | } 58 | })" 59 | ) 60 | ); 61 | 62 | // diff mutex type, for variety 63 | auto test = make_local_shared( 64 | MetaFormat::JSON, 65 | R"({ 66 | "foo": "a" 67 | })" 68 | ); 69 | 70 | REQUIRE_NOTHROW(schema->validate(test)); 71 | REQUIRE(schema->check(test)); 72 | test->set("foo", "d"); 73 | { 74 | // don't logging the error output (will still throw) 75 | Log::Silencer ls(Log::Silencer::ERRORS); 76 | REQUIRE_THROWS(schema->validate(test)); 77 | } 78 | REQUIRE_FALSE(schema->check(test)); 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /tests/slice.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../kit/kit.h" 3 | using namespace std; 4 | 5 | TEST_CASE("Slice","[slice]") { 6 | SECTION("slice") { 7 | vector v = {1,2,3,4,5}; 8 | REQUIRE(kit::slice(v,1,4) == (vector{2,3,4})); 9 | REQUIRE(kit::slice(v,1,10) == (vector{2,3,4,5})); 10 | REQUIRE(kit::slice(v,1,10) == (vector{2,3,4,5})); 11 | REQUIRE(kit::slice(v,1,-1) == (vector{2,3,4})); 12 | REQUIRE(kit::slice(v,-3,-1) == (vector{3,4})); 13 | 14 | REQUIRE(kit::slice(v,0, 10, 3) == (vector{1,4})); 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /tests/suite.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | namespace std { 3 | inline bool uncaught_exception() noexcept(true) {return current_exception() != nullptr;} 4 | } 5 | 6 | #define CATCH_CONFIG_MAIN 7 | #include 8 | -------------------------------------------------------------------------------- /toys/premake4.lua: -------------------------------------------------------------------------------- 1 | solution("kit") 2 | configurations {"debug", "release"} 3 | 4 | targetdir("bin") 5 | 6 | defines { 7 | "GLM_FORCE_CTOR_INIT", 8 | "GLM_ENABLE_EXPERIMENTAL", 9 | "GLM_FORCE_RADIANS", 10 | "NOMINMAX" 11 | } 12 | 13 | configuration "debug" 14 | defines { "DEBUG" } 15 | flags { "Symbols" } 16 | configuration "release" 17 | defines { "NDEBUG" } 18 | flags { "OptimizeSpeed" } 19 | 20 | language("C++") 21 | links { 22 | "pthread", 23 | "boost_thread", 24 | "SDL2", 25 | "boost_system", 26 | "boost_regex", 27 | "boost_filesystem", 28 | "boost_coroutine", 29 | "jsoncpp" 30 | } 31 | defines { "BACKWARD_HAS_BFD=1" } 32 | includedirs { 33 | "../lib/local_shared_ptr", 34 | "../vendor/include/", 35 | "/usr/include/cpp-netlib/" 36 | } 37 | 38 | configuration {"debug"} 39 | links { 40 | "bfd", 41 | "iberty" 42 | } 43 | linkoptions { "`llvm-config --libs core` `llvm-config --ldflags`" } 44 | configuration {} 45 | 46 | configuration { "gmake" } 47 | --buildoptions { "-std=c++11", "-pedantic", "-Wall", "-Wextra" } 48 | buildoptions { "-std=c++11" } 49 | configuration { "macosx" } 50 | buildoptions { "-U__STRICT_ANSI__", "-stdlib=libc++" } 51 | linkoptions { "-stdlib=libc++" } 52 | configuration {} 53 | 54 | files { "../kit/**.cpp" } 55 | 56 | project("echo") 57 | kind("ConsoleApp") 58 | files { "src/echo.cpp" } 59 | 60 | project("stability") 61 | kind("ConsoleApp") 62 | files { "src/stability.cpp" } 63 | 64 | project("chat") 65 | kind("ConsoleApp") 66 | files { "src/chat.cpp" } 67 | 68 | -------------------------------------------------------------------------------- /toys/sg.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "env": { 4 | "CXX": "clang++" 5 | }, 6 | "makefile_params": [ 7 | "CXX='clang++'" 8 | ] 9 | }, 10 | 11 | "dependencies": { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /toys/src/chat.cpp: -------------------------------------------------------------------------------- 1 | #include "../../kit/net/net.h" 2 | #include "../../kit/log/log.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | // This is a basic chat server. To chat with other clients, use netcat: 10 | // nc localhost 1337 11 | // The first message you type will be your username. 12 | 13 | // Notes: 14 | // - TCP-based 15 | // - Async 16 | // - single strand/circuit for simplicity (hence no added locks/atomics) 17 | 18 | struct Client 19 | { 20 | Client(shared_ptr socket): 21 | socket(socket) 22 | {} 23 | 24 | Client(const Client&) = default; 25 | Client(Client&&) = default; 26 | Client& operator=(const Client&) = default; 27 | Client& operator=(Client&&) = default; 28 | 29 | string name; 30 | shared_ptr socket; 31 | }; 32 | 33 | void broadcast(std::string text, vector> clients) 34 | { 35 | LOG(text); 36 | for(auto&& client: clients) 37 | AWAIT(client->socket->send(text + "\n")); 38 | } 39 | 40 | int main(int argc, char** argv) 41 | { 42 | unsigned short port = 1337; 43 | try{ 44 | if(argc > 1) 45 | port = boost::lexical_cast(argv[1]); 46 | }catch(...){} 47 | 48 | auto server = make_shared(); 49 | server->open(); 50 | server->bind(port); 51 | server->listen(); 52 | 53 | vector> clients; 54 | 55 | auto fut = MX[0].coro([&]{ 56 | for(;;) 57 | { 58 | auto socket = make_shared(AWAIT(server->accept())); 59 | MX[0].coro([&, socket]{ 60 | 61 | // add this client 62 | auto client = make_shared(socket); 63 | clients.push_back(client); 64 | 65 | try{ 66 | for(;;) 67 | { 68 | // recv message from client 69 | auto msg = AWAIT(socket->recv()); 70 | 71 | boost::algorithm::trim(msg); 72 | 73 | // set client name 74 | if(client->name.empty() && not msg.empty()) { 75 | client->name = msg; 76 | broadcast(client->name + " connected.", clients); 77 | continue; 78 | } 79 | 80 | // send chat message 81 | broadcast(client->name + ": " + msg, clients); 82 | } 83 | }catch(const socket_exception& e){ 84 | if(not client->name.empty()) 85 | LOGf("%s disconnected (%s)", client->name % e.what()); 86 | } 87 | }); 88 | } 89 | }); 90 | 91 | fut.get(); 92 | return 0; 93 | } 94 | 95 | -------------------------------------------------------------------------------- /toys/src/echo.cpp: -------------------------------------------------------------------------------- 1 | #include "../../kit/net/net.h" 2 | #include "../../kit/log/log.h" 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | int main(int argc, char** argv) 9 | { 10 | unsigned short port = 1337; 11 | try{ 12 | if(argc > 1) 13 | port = boost::lexical_cast(argv[1]); 14 | }catch(...){} 15 | 16 | auto server = make_shared(); 17 | server->open(); 18 | server->bind(port); 19 | server->listen(); 20 | 21 | int client_ids = 0; 22 | 23 | auto fut = MX[0].coro([&]{ 24 | for(;;) 25 | { 26 | LOG("awaiting connection"); 27 | auto client = make_shared(AWAIT(server->accept())); 28 | MX[0].coro([&, client]{ 29 | int client_id = client_ids++; 30 | LOGf("client %s connected", client_id); 31 | try{ 32 | for(;;) 33 | AWAIT(client->send(AWAIT(client->recv()))); 34 | }catch(const socket_exception& e){ 35 | LOGf("client %s disconnected (%s)", client_id % e.what()); 36 | } 37 | }); 38 | } 39 | }); 40 | 41 | fut.get(); 42 | return 0; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /toys/src/stability.cpp: -------------------------------------------------------------------------------- 1 | #include "../../kit/async/async.h" 2 | #include "../../kit/log/log.h" 3 | #include 4 | using namespace std; 5 | 6 | int main(int argc, char** argv) 7 | { 8 | srand(time(NULL)); 9 | for(unsigned i=0;i([i,c]{ 16 | for(;;){ 17 | YIELD(); 18 | } 19 | }); 20 | } 21 | 22 | return 0; 23 | } 24 | 25 | --------------------------------------------------------------------------------