Listener object is better than 3 lambdas + handle
[lsnes.git] / src / library / command.cpp
blob2926d4935081479c4358f6c8accd24b0148143d0
1 #include "command.hpp"
2 #include "globalwrap.hpp"
3 #include "integer-pool.hpp"
4 #include "minmax.hpp"
5 #include "register-queue.hpp"
6 #include "stateobject.hpp"
7 #include "string.hpp"
8 #include "zip.hpp"
9 #include <iostream>
10 #include <cstdlib>
12 namespace command
14 namespace
16 struct run_script : public base
18 run_script(group& group, std::ostream*& _output)
19 : base(group, "run-script", true), in_group(group), output(_output)
23 ~run_script() throw()
27 void invoke(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
29 if(filename == "") {
30 (*output) << "Syntax: run-script <scriptfile>" << std::endl;
31 return;
33 std::istream* o = NULL;
34 try {
35 o = &zip::openrel(filename, "");
36 (*output) << "Running '" << std::string(filename) << "'" << std::endl;
37 std::string line;
38 while(std::getline(*o, line))
39 in_group.invoke(line);
40 delete o;
41 } catch(std::exception& e) {
42 delete o;
43 throw;
47 std::string get_short_help() throw(std::bad_alloc)
49 return "Run file as a script";
52 std::string get_long_help() throw(std::bad_alloc)
54 return "Syntax: run-script <file>\nRuns file <file> just as it would have been entered in "
55 "the command line\n";
58 group& in_group;
59 std::ostream*& output;
62 void default_oom_panic()
64 std::cerr << "PANIC: Fatal error, can't continue: Out of memory." << std::endl;
65 exit(1);
68 threads::rlock* global_lock;
69 threads::rlock& get_cmd_lock()
71 if(!global_lock) global_lock = new threads::rlock;
72 return *global_lock;
75 struct set_internal
77 std::set<set_listener*> callbacks;
78 std::map<std::string, factory_base*> commands;
81 struct group_internal
83 std::map<std::string, base*> commands;
84 std::set<set*> set_handles;
87 typedef stateobject::type<set, set_internal> set_internal_t;
88 typedef stateobject::type<group, group_internal> group_internal_t;
91 set_listener::~set_listener()
95 void factory_base::_factory_base(set& _set, const std::string& cmd) throw(std::bad_alloc)
97 threads::arlock h(get_cmd_lock());
98 in_set = &_set;
99 in_set->do_register(commandname = cmd, *this);
102 factory_base::~factory_base() throw()
104 threads::arlock h(get_cmd_lock());
105 if(in_set)
106 in_set->do_unregister(commandname, *this);
109 void factory_base::set_died() throw()
111 //The lock is held by assumption.
112 in_set = NULL;
115 base::base(group& group, const std::string& cmd, bool dynamic) throw(std::bad_alloc)
117 in_group = &group;
118 is_dynamic = dynamic;
119 threads::arlock h(get_cmd_lock());
120 in_group->do_register(commandname = cmd, *this);
123 base::~base() throw()
125 threads::arlock h(get_cmd_lock());
126 if(in_group)
127 in_group->do_unregister(commandname, *this);
130 void base::group_died() throw()
132 threads::arlock h(get_cmd_lock());
133 in_group = NULL;
134 //If dynamic, we aren't needed anymore.
135 if(is_dynamic) delete this;
138 std::string base::get_short_help() throw(std::bad_alloc)
140 return "No description available";
143 std::string base::get_long_help() throw(std::bad_alloc)
145 return "No help available on command " + commandname;
148 set::set() throw(std::bad_alloc)
152 set::~set() throw()
154 auto state = set_internal_t::get_soft(this);
155 if(!state) return;
156 threads::arlock h(get_cmd_lock());
157 //Call all DCBs on all factories.
158 for(auto i : state->commands)
159 for(auto j : state->callbacks)
160 j->destroy(*this, i.first);
161 //Call all TCBs.
162 for(auto j : state->callbacks)
163 j->kill(*this);
164 //Notify all factories that base set died.
165 for(auto i : state->commands)
166 i.second->set_died();
167 //We assume factories look after themselves, so we don't destroy those.
168 set_internal_t::clear(this);
171 void set::do_register(const std::string& name, factory_base& cmd) throw(std::bad_alloc)
173 threads::arlock h(get_cmd_lock());
174 auto& state = set_internal_t::get(this);
175 if(state.commands.count(name)) {
176 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
177 return;
179 state.commands[name] = &cmd;
180 //Call all CCBs on this.
181 for(auto i : state.callbacks)
182 i->create(*this, name, cmd);
185 void set::do_unregister(const std::string& name, factory_base& cmd) throw(std::bad_alloc)
187 threads::arlock h(get_cmd_lock());
188 auto state = set_internal_t::get_soft(this);
189 if(!state) return;
190 if(!state->commands.count(name) || state->commands[name] != &cmd) return; //Not this.
191 state->commands.erase(name);
192 //Call all DCBs on this.
193 for(auto i : state->callbacks)
194 i->destroy(*this, name);
197 void set::add_callback(set_listener& listener)
198 throw(std::bad_alloc)
200 threads::arlock h(get_cmd_lock());
201 auto& state = set_internal_t::get(this);
202 state.callbacks.insert(&listener);
203 //To avoid races, call CCBs on all factories for this.
204 for(auto j : state.commands)
205 listener.create(*this, j.first, *j.second);
208 void set::drop_callback(set_listener& listener) throw()
210 threads::arlock h(get_cmd_lock());
211 auto state = set_internal_t::get_soft(this);
212 if(!state) return;
213 if(state->callbacks.count(&listener)) {
214 //To avoid races, call DCBs on all factories for this.
215 for(auto j : state->commands)
216 listener.destroy(*this, j.first);
217 state->callbacks.erase(&listener);
221 std::map<std::string, factory_base*> set::get_commands()
223 threads::arlock h(get_cmd_lock());
224 auto state = set_internal_t::get_soft(this);
225 if(!state) return std::map<std::string, factory_base*>();
226 return state->commands;
229 group::group() throw(std::bad_alloc)
230 : _listener(*this)
232 oom_panic_routine = default_oom_panic;
233 output = &std::cerr;
234 //The builtin commands.
235 builtin[0] = new run_script(*this, output);
238 group::~group() throw()
240 auto state = group_internal_t::get_soft(this);
241 if(!state) return;
242 threads::arlock h(get_cmd_lock());
244 //Notify all bases that base group died.
245 //Builtin commands delete themselves on parent group dying.
246 for(auto i : state->commands)
247 i.second->group_died();
249 //Drop all callbacks.
250 for(auto i : state->set_handles)
251 i->drop_callback(_listener);
252 //We assume all bases that need destroying have already been destroyed.
253 group_internal_t::clear(this);
256 void group::invoke(const std::string& cmd) throw()
258 auto state = group_internal_t::get_soft(this);
259 if(!state) return;
260 try {
261 std::string cmd2 = strip_CR(cmd);
262 if(cmd2 == "?") {
263 //The special ? command.
264 threads::arlock lock(get_cmd_lock());
265 for(auto i : state->commands)
266 (*output) << i.first << ": " << i.second->get_short_help() << std::endl;
267 return;
269 if(firstchar(cmd2) == '?') {
270 //?command.
271 threads::arlock lock(get_cmd_lock());
272 std::string rcmd = cmd2.substr(1, min(cmd2.find_first_of(" \t"), cmd2.length()));
273 if(firstchar(rcmd) != '*') {
274 //This may be an alias.
275 if(aliases.count(rcmd)) {
276 //Yup.
277 (*output) << rcmd << " is an alias for: " << std::endl;
278 size_t j = 0;
279 for(auto i : aliases[rcmd])
280 (*output) << "#" << (++j) << ": " << i << std::endl;
281 return;
283 } else
284 rcmd = rcmd.substr(1);
285 if(!state->commands.count(rcmd))
286 (*output) << "Unknown command '" << rcmd << "'" << std::endl;
287 else
288 (*output) << state->commands[rcmd]->get_long_help() << std::endl;
289 return;
291 bool may_be_alias_expanded = true;
292 if(firstchar(cmd2) == '*') {
293 may_be_alias_expanded = false;
294 cmd2 = cmd2.substr(1);
296 //Now this gets painful as command handlers must not be invoked with lock held.
297 if(may_be_alias_expanded) {
298 std::list<std::string> aexp;
300 threads::arlock lock(get_cmd_lock());
301 if(!aliases.count(cmd))
302 goto not_alias;
303 aexp = aliases[cmd2];
305 for(auto i : aexp)
306 invoke(i);
307 return;
309 not_alias:
310 try {
311 size_t split = cmd2.find_first_of(" \t");
312 std::string rcmd = cmd2.substr(0, min(split, cmd2.length()));
313 std::string args = cmd2.substr(min(cmd2.find_first_not_of(" \t", split), cmd2.length()));
314 base* cmdh = NULL;
316 threads::arlock lock(get_cmd_lock());
317 if(!state->commands.count(rcmd)) {
318 (*output) << "Unknown command '" << rcmd << "'" << std::endl;
319 return;
321 cmdh = state->commands[rcmd];
323 if(command_stack.count(cmd2))
324 throw std::runtime_error("Recursive command invocation");
325 command_stack.insert(cmd2);
326 cmdh->invoke(args);
327 command_stack.erase(cmd2);
328 return;
329 } catch(std::bad_alloc& e) {
330 oom_panic_routine();
331 } catch(std::exception& e) {
332 (*output) << "Error: " << e.what() << std::endl;
333 command_stack.erase(cmd2);
334 return;
336 } catch(std::bad_alloc& e) {
337 oom_panic_routine();
341 std::set<std::string> group::get_aliases() throw(std::bad_alloc)
343 threads::arlock lock(get_cmd_lock());
344 std::set<std::string> r;
345 for(auto i : aliases)
346 r.insert(i.first);
347 return r;
350 std::string group::get_alias_for(const std::string& aname) throw(std::bad_alloc)
352 threads::arlock lock(get_cmd_lock());
353 if(!valid_alias_name(aname))
354 return "";
355 if(aliases.count(aname)) {
356 std::string x;
357 for(auto i : aliases[aname])
358 x = x + i + "\n";
359 return x;
360 } else
361 return "";
364 void group::set_alias_for(const std::string& aname, const std::string& avalue) throw(std::bad_alloc)
366 threads::arlock lock(get_cmd_lock());
367 if(!valid_alias_name(aname))
368 return;
369 std::list<std::string> newlist;
370 size_t avitr = 0;
371 while(avitr < avalue.length()) {
372 size_t nextsplit = min(avalue.find_first_of("\n", avitr), avalue.length());
373 std::string x = strip_CR(avalue.substr(avitr, nextsplit - avitr));
374 if(x.length() > 0)
375 newlist.push_back(x);
376 avitr = nextsplit + 1;
378 if(newlist.empty())
379 aliases.erase(aname);
380 else
381 aliases[aname] = newlist;
384 bool group::valid_alias_name(const std::string& aliasname) throw(std::bad_alloc)
386 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
387 return false;
388 if(aliasname.find_first_of(" \t") < aliasname.length())
389 return false;
390 return true;
393 void group::do_register(const std::string& name, base& cmd) throw(std::bad_alloc)
395 threads::arlock h(get_cmd_lock());
396 auto& state = group_internal_t::get(this);
397 if(state.commands.count(name))
398 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
399 state.commands[name] = &cmd;
402 void group::do_unregister(const std::string& name, base& cmd) throw(std::bad_alloc)
404 threads::arlock h(get_cmd_lock());
405 auto state = group_internal_t::get_soft(this);
406 if(!state) return;
407 if(!state->commands.count(name) || state->commands[name] != &cmd) return;
408 state->commands.erase(name);
411 void group::set_output(std::ostream& s)
413 output = &s;
416 void group::set_oom_panic(void (*fn)())
418 if(fn)
419 oom_panic_routine = fn;
420 else
421 oom_panic_routine = default_oom_panic;
424 void group::add_set(set& s) throw(std::bad_alloc)
426 threads::arlock h(get_cmd_lock());
427 auto& state = group_internal_t::get(this);
428 if(state.set_handles.count(&s)) return;
429 try {
430 state.set_handles.insert(&s);
431 s.add_callback(_listener);
432 } catch(...) {
433 state.set_handles.erase(&s);
437 void group::drop_set(set& s) throw()
439 threads::arlock h(get_cmd_lock());
440 auto state = group_internal_t::get_soft(this);
441 if(!state) return;
442 //Drop the callback. This unregisters all.
443 s.drop_callback(_listener);
444 state->set_handles.erase(&s);
447 group::listener::listener(group& _grp)
448 : grp(_grp)
452 group::listener::~listener()
456 void group::listener::create(set& s, const std::string& name, factory_base& cmd)
458 threads::arlock h(get_cmd_lock());
459 cmd.make(grp);
462 void group::listener::destroy(set& s, const std::string& name)
464 threads::arlock h(get_cmd_lock());
465 auto state = group_internal_t::get_soft(&grp);
466 if(!state) return;
467 state->commands.erase(name);
470 void group::listener::kill(set& s)
472 threads::arlock h(get_cmd_lock());
473 auto state = group_internal_t::get_soft(&grp);
474 if(!state) return;
475 state->set_handles.erase(&s);
478 template<>
479 void invoke_fn(void (*fn)(const std::string& args), const std::string& args)
481 fn(args);
484 template<>
485 void invoke_fn(void (*fn)(), const std::string& args)
487 if(args != "")
488 throw std::runtime_error("This command does not take arguments");
489 fn();
492 template<>
493 void invoke_fn(void (*fn)(struct arg_filename a), const std::string& args)
495 if(args == "")
496 throw std::runtime_error("Filename required");
497 arg_filename b;
498 b.v = args;
499 fn(b);