Actually call on_reset callback
[lsnes.git] / src / library / command.cpp
blobb757ea3b23ab1acbc910817c999852b07edb0549
1 #include "command.hpp"
2 #include "globalwrap.hpp"
3 #include "integer-pool.hpp"
4 #include "minmax.hpp"
5 #include "stateobject.hpp"
6 #include "string.hpp"
7 #include "threads.hpp"
8 #include "zip.hpp"
9 #include <functional>
10 #include <iostream>
11 #include <cstdlib>
13 namespace command
15 namespace
17 struct run_script : public base
19 run_script(group& group, std::ostream*& _output)
20 : base(group, "run-script", true), in_group(group), output(_output)
24 ~run_script() throw()
28 void invoke(const std::string& filename)
30 if(filename == "") {
31 (*output) << "Syntax: run-script <scriptfile>" << std::endl;
32 return;
34 std::istream* o = NULL;
35 try {
36 o = &zip::openrel(filename, "");
37 (*output) << "Running '" << std::string(filename) << "'" << std::endl;
38 std::string line;
39 while(std::getline(*o, line))
40 in_group.invoke(line);
41 delete o;
42 } catch(std::exception& e) {
43 delete o;
44 throw;
48 std::string get_short_help()
50 return "Run file as a script";
53 std::string get_long_help()
55 return "Syntax: run-script <file>\nRuns file <file> just as it would have been entered in "
56 "the command line\n";
59 group& in_group;
60 std::ostream*& output;
63 void default_oom_panic()
65 std::cerr << "PANIC: Fatal error, can't continue: Out of memory." << std::endl;
66 exit(1);
69 threads::rlock* global_lock;
70 threads::rlock& get_cmd_lock()
72 if(!global_lock) global_lock = new threads::rlock;
73 return *global_lock;
76 struct set_internal
78 std::set<set::listener*> callbacks;
79 std::map<std::string, factory_base*> commands;
82 struct group_internal
84 std::map<std::string, base*> commands;
85 std::set<set*> set_handles;
88 typedef stateobject::type<set, set_internal> set_internal_t;
89 typedef stateobject::type<group, group_internal> group_internal_t;
92 set::listener::~listener()
96 void factory_base::_factory_base(set& _set, const std::string& cmd)
98 threads::arlock h(get_cmd_lock());
99 in_set = &_set;
100 in_set->do_register(commandname = cmd, *this);
103 factory_base::~factory_base() throw()
105 threads::arlock h(get_cmd_lock());
106 if(in_set)
107 in_set->do_unregister(commandname, *this);
110 void factory_base::set_died() throw()
112 //The lock is held by assumption.
113 in_set = NULL;
116 base::base(group& group, const std::string& cmd, bool dynamic)
118 in_group = &group;
119 is_dynamic = dynamic;
120 threads::arlock h(get_cmd_lock());
121 in_group->do_register(commandname = cmd, *this);
124 base::~base() throw()
126 threads::arlock h(get_cmd_lock());
127 if(in_group)
128 in_group->do_unregister(commandname, *this);
131 void base::group_died() throw()
133 threads::arlock h(get_cmd_lock());
134 in_group = NULL;
135 //If dynamic, we aren't needed anymore.
136 if(is_dynamic) delete this;
139 std::string base::get_short_help()
141 return "No description available";
144 std::string base::get_long_help()
146 return "No help available on command " + commandname;
149 set::set()
153 set::~set() throw()
155 auto state = set_internal_t::get_soft(this);
156 if(!state) return;
157 threads::arlock h(get_cmd_lock());
158 //Call all DCBs on all factories.
159 for(auto i : state->commands)
160 for(auto j : state->callbacks)
161 j->destroy(*this, i.first);
162 //Call all TCBs.
163 for(auto j : state->callbacks)
164 j->kill(*this);
165 //Notify all factories that base set died.
166 for(auto i : state->commands)
167 i.second->set_died();
168 //We assume factories look after themselves, so we don't destroy those.
169 set_internal_t::clear(this);
172 void set::do_register(const std::string& name, factory_base& cmd)
174 threads::arlock h(get_cmd_lock());
175 auto& state = set_internal_t::get(this);
176 if(state.commands.count(name)) {
177 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
178 return;
180 state.commands[name] = &cmd;
181 //Call all CCBs on this.
182 for(auto i : state.callbacks)
183 i->create(*this, name, cmd);
186 void set::do_unregister(const std::string& name, factory_base& cmd)
188 threads::arlock h(get_cmd_lock());
189 auto state = set_internal_t::get_soft(this);
190 if(!state) return;
191 if(!state->commands.count(name) || state->commands[name] != &cmd) return; //Not this.
192 state->commands.erase(name);
193 //Call all DCBs on this.
194 for(auto i : state->callbacks)
195 i->destroy(*this, name);
198 void set::add_callback(set::listener& listener)
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 group::group()
222 : _listener(*this)
224 oom_panic_routine = default_oom_panic;
225 output = &std::cerr;
226 //The builtin commands.
227 builtin[0] = new run_script(*this, output);
230 group::~group() throw()
232 auto state = group_internal_t::get_soft(this);
233 if(!state) return;
234 threads::arlock h(get_cmd_lock());
236 //Notify all bases that base group died.
237 //Builtin commands delete themselves on parent group dying.
238 for(auto i : state->commands)
239 i.second->group_died();
241 //Drop all callbacks.
242 for(auto i : state->set_handles)
243 i->drop_callback(_listener);
244 //We assume all bases that need destroying have already been destroyed.
245 group_internal_t::clear(this);
248 void group::invoke(const std::string& cmd) throw()
250 auto state = group_internal_t::get_soft(this);
251 if(!state) return;
252 try {
253 std::string cmd2 = strip_CR(cmd);
254 if(cmd2 == "?") {
255 //The special ? command.
256 threads::arlock lock(get_cmd_lock());
257 for(auto i : state->commands)
258 (*output) << i.first << ": " << i.second->get_short_help() << std::endl;
259 return;
261 if(firstchar(cmd2) == '?') {
262 //?command.
263 threads::arlock lock(get_cmd_lock());
264 std::string rcmd = cmd2.substr(1, min(cmd2.find_first_of(" \t"), cmd2.length()));
265 if(firstchar(rcmd) != '*') {
266 //This may be an alias.
267 if(aliases.count(rcmd)) {
268 //Yup.
269 (*output) << rcmd << " is an alias for: " << std::endl;
270 size_t j = 0;
271 for(auto i : aliases[rcmd])
272 (*output) << "#" << (++j) << ": " << i << std::endl;
273 return;
275 } else
276 rcmd = rcmd.substr(1);
277 if(!state->commands.count(rcmd))
278 (*output) << "Unknown command '" << rcmd << "'" << std::endl;
279 else
280 (*output) << state->commands[rcmd]->get_long_help() << std::endl;
281 return;
283 bool may_be_alias_expanded = true;
284 if(firstchar(cmd2) == '*') {
285 may_be_alias_expanded = false;
286 cmd2 = cmd2.substr(1);
288 //Now this gets painful as command handlers must not be invoked with lock held.
289 if(may_be_alias_expanded) {
290 std::list<std::string> aexp;
292 threads::arlock lock(get_cmd_lock());
293 if(!aliases.count(cmd))
294 goto not_alias;
295 aexp = aliases[cmd2];
297 for(auto i : aexp)
298 invoke(i);
299 return;
301 not_alias:
302 try {
303 size_t split = cmd2.find_first_of(" \t");
304 std::string rcmd = cmd2.substr(0, min(split, cmd2.length()));
305 std::string args = cmd2.substr(min(cmd2.find_first_not_of(" \t", split), cmd2.length()));
306 invoke(rcmd, args);
307 return;
308 } catch(std::bad_alloc& e) {
309 oom_panic_routine();
310 } catch(std::exception& e) {
311 (*output) << "Error[" << cmd2 << "]: " << e.what() << std::endl;
312 command_stack.erase(cmd2);
313 return;
315 } catch(std::bad_alloc& e) {
316 oom_panic_routine();
320 void group::invoke(const std::string& cmd, const std::string& args) throw()
322 auto state = group_internal_t::get_soft(this);
323 if(!state) return;
324 try {
325 std::string ckey = cmd + " " + args;
326 try {
327 base* cmdh = NULL;
329 threads::arlock lock(get_cmd_lock());
330 if(!state->commands.count(cmd)) {
331 (*output) << "Unknown command '" << cmd << "'" << std::endl;
332 return;
334 cmdh = state->commands[cmd];
336 if(command_stack.count(ckey))
337 throw std::runtime_error("Recursive command invocation");
338 command_stack.insert(ckey);
339 cmdh->invoke(args);
340 command_stack.erase(ckey);
341 return;
342 } catch(std::bad_alloc& e) {
343 oom_panic_routine();
344 } catch(std::exception& e) {
345 (*output) << "Error[" << ckey << "]: " << e.what() << std::endl;
346 command_stack.erase(ckey);
347 return;
349 } catch(std::bad_alloc& e) {
350 oom_panic_routine();
354 std::set<std::string> group::get_aliases()
356 threads::arlock lock(get_cmd_lock());
357 std::set<std::string> r;
358 for(auto i : aliases)
359 r.insert(i.first);
360 return r;
363 std::string group::get_alias_for(const std::string& aname)
365 threads::arlock lock(get_cmd_lock());
366 if(!valid_alias_name(aname))
367 return "";
368 if(aliases.count(aname)) {
369 std::string x;
370 for(auto i : aliases[aname])
371 x = x + i + "\n";
372 return x;
373 } else
374 return "";
377 void group::set_alias_for(const std::string& aname, const std::string& avalue)
379 threads::arlock lock(get_cmd_lock());
380 if(!valid_alias_name(aname))
381 return;
382 std::list<std::string> newlist;
383 size_t avitr = 0;
384 while(avitr < avalue.length()) {
385 size_t nextsplit = min(avalue.find_first_of("\n", avitr), avalue.length());
386 std::string x = strip_CR(avalue.substr(avitr, nextsplit - avitr));
387 if(x.length() > 0)
388 newlist.push_back(x);
389 avitr = nextsplit + 1;
391 if(newlist.empty())
392 aliases.erase(aname);
393 else
394 aliases[aname] = newlist;
397 bool group::valid_alias_name(const std::string& aliasname)
399 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
400 return false;
401 if(aliasname.find_first_of(" \t") < aliasname.length())
402 return false;
403 return true;
406 void group::do_register(const std::string& name, base& cmd)
408 threads::arlock h(get_cmd_lock());
409 auto& state = group_internal_t::get(this);
410 if(state.commands.count(name))
411 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
412 state.commands[name] = &cmd;
415 void group::do_unregister(const std::string& name, base& cmd)
417 threads::arlock h(get_cmd_lock());
418 auto state = group_internal_t::get_soft(this);
419 if(!state) return;
420 if(!state->commands.count(name) || state->commands[name] != &cmd) return;
421 state->commands.erase(name);
424 void group::set_output(std::ostream& s)
426 output = &s;
429 void group::set_oom_panic(void (*fn)())
431 if(fn)
432 oom_panic_routine = fn;
433 else
434 oom_panic_routine = default_oom_panic;
437 void group::add_set(set& s)
439 threads::arlock h(get_cmd_lock());
440 auto& state = group_internal_t::get(this);
441 if(state.set_handles.count(&s)) return;
442 try {
443 state.set_handles.insert(&s);
444 s.add_callback(_listener);
445 } catch(...) {
446 state.set_handles.erase(&s);
450 void group::drop_set(set& s) throw()
452 threads::arlock h(get_cmd_lock());
453 auto state = group_internal_t::get_soft(this);
454 if(!state) return;
455 //Drop the callback. This unregisters all.
456 s.drop_callback(_listener);
457 state->set_handles.erase(&s);
460 group::listener::listener(group& _grp)
461 : grp(_grp)
465 group::listener::~listener()
469 void group::listener::create(set& s, const std::string& name, factory_base& cmd)
471 threads::arlock h(get_cmd_lock());
472 cmd.make(grp);
475 void group::listener::destroy(set& s, const std::string& name)
477 threads::arlock h(get_cmd_lock());
478 auto state = group_internal_t::get_soft(&grp);
479 if(!state) return;
480 state->commands.erase(name);
483 void group::listener::kill(set& s)
485 threads::arlock h(get_cmd_lock());
486 auto state = group_internal_t::get_soft(&grp);
487 if(!state) return;
488 state->set_handles.erase(&s);
491 template<>
492 void invoke_fn(std::function<void(const std::string& args)> fn, const std::string& args)
494 fn(args);
497 template<>
498 void invoke_fn(std::function<void()> fn, const std::string& args)
500 if(args != "")
501 throw std::runtime_error("This command does not take arguments");
502 fn();
505 template<>
506 void invoke_fn(std::function<void(struct arg_filename a)> fn, const std::string& args)
508 if(args == "")
509 throw std::runtime_error("Filename required");
510 arg_filename b;
511 b.v = args;
512 fn(b);