Make Scheme hooks and timers threadsafe
[clinton/bobotpp.git] / source / BotInterp.C
CommitLineData
cb21075d 1// BotInterp.C -*- C++ -*-
2// Copyright (c) 1998 Etienne BERNARD
46af1667 3// Copyright (C) 2002,2005,2008 Clinton Ebadi
cb21075d 4
5// This program is free software; you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation; either version 2 of the License, or
8// (at your option) any later version.
9
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with this program; if not, write to the Free Software
ae97d6ec 17// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18// 02110-1301, USA.
cb21075d 19
20#ifdef HAVE_CONFIG_H
21#include "config.h"
22#endif
23
3c37f9a8 24#include <algorithm>
25
cb21075d 26#include "Utils.H"
27#include "Bot.H"
28#include "BotInterp.H"
29
30#ifdef USESCRIPTS
31
32#include <libguile.h>
33extern "C"
34{
35#include <libguile/regex-posix.h>
36}
37
38BotInterp::BotInterp(Bot *b, String fn)
3c37f9a8 39 : bot(b), counter(0), timer_mutex (true)
cb21075d 40{
db098a03 41 logPort = scm_open_file (Utils::str2scm (fn),
42 Utils::str2scm ("a"));
43
cb21075d 44 scm_gc_protect_object(logPort);
45}
46
47void
48BotInterp::Execute(String command)
49{
50 Interp::Execute(bot, command);
51}
52
53void
54BotInterp::LoadScript(String filename)
55{
56 Interp::LoadScript(bot, filename);
57}
58
d56bdd22 59SCM
60BotInterp::ScriptLog()
cb21075d 61{
d56bdd22 62 return logPort;
cb21075d 63}
64
be3612f3 65namespace
66{
67 bool hptr_lt (const Hook* h, const Hook* h1)
68 // Hook Pointer less than
69 // Used to sort the Hooks list
70 { return *h < *h1; }
71}
72
cb21075d 73bool
fd7440f1 74BotInterp::AddHook(int hooktype, SCM regex, SCM function, int pri, bool fall,
3c37f9a8 75 String name)
76{
cb21075d 77 if (scm_string_p(regex) == SCM_BOOL_F)
78 return false;
a6339323 79 String rx = Utils::to_upper (Utils::scm2str (regex));
46af1667 80 // fixme: really ought to use scm_c_module_lookup with bot module
81 SCM r = scm_make_regexp (regex,
82 scm_list_n (scm_variable_ref (scm_c_lookup ("regexp/icase")),
83 SCM_UNDEFINED));
cb21075d 84 scm_gc_protect_object(r);
85 scm_gc_protect_object(function);
3c37f9a8 86
87 BotLock hook_lock (hook_mutex);
88
cb21075d 89 // First, we check if an hook doesn't exist yet
90 std::list<Hook *>::iterator it = hooksMap[hooktype].begin();
91 std::list<Hook *>::iterator it2 = hooksMap[hooktype].end();
be3612f3 92
cb21075d 93 for ( ; it != it2; ++it)
94 // It exists, we replace it.
fd7440f1 95 if ((*it)->regex_str == rx && (*it)->name == name) {
cb21075d 96 scm_gc_unprotect_object((*it)->function);
97 scm_gc_unprotect_object (r);
98 (*it)->function = function;
99 (*it)->priority = pri;
100 (*it)->fallthru = fall;
be3612f3 101 hooksMap[hooktype].sort (hptr_lt);
cb21075d 102 return true;
103 }
104 // It does not exist, we create it
105 hooksMap[hooktype].push_back (new Hook(hooktype, rx, r,
fd7440f1 106 function, pri, fall, name));
be3612f3 107 hooksMap[hooktype].sort (hptr_lt);
cb21075d 108 return true;
109}
110
111bool
112BotInterp::RunHooks(int hooktype, String match, SCM args)
113{
3c37f9a8 114 BotLock hook_lock (hook_mutex);
115
cb21075d 116 SCM result;
3c37f9a8 117
ad529fde 118 // We want to execute higher priority hooks first, so we start at
119 // the end of the list instead of the beggining
120 std::list<Hook *>::reverse_iterator it = hooksMap[hooktype].rbegin();
121 std::list<Hook *>::reverse_iterator it2 = hooksMap[hooktype].rend();
3c37f9a8 122
cb21075d 123 wrapper_data wd;
124 wd.args = args;
3c37f9a8 125
cb21075d 126 for ( ; it != it2; ++it) {
a6339323 127 if (scm_regexp_exec((*it)->regex, Utils::str2scm (match),
cb21075d 128 SCM_UNDEFINED, SCM_UNDEFINED) != SCM_BOOL_F)
129 {
130 wd.func = (*it)->function;
d56bdd22 131 result = scm_internal_catch(SCM_BOOL_T,
ae97d6ec 132 (scm_t_catch_body)
133 Interp::LazyApplyWrapper,
d56bdd22 134 static_cast<void *> (&wd),
ae97d6ec 135 (scm_t_catch_handler) Interp::EmptyHandler, 0);
cb21075d 136 if (! (*it)->fallthru)
137 break;
138 }
139 }
3c37f9a8 140
cb21075d 141 return true;
142}
143
144SCM
145BotInterp::AddTimer(int delay, SCM function)
146{
3c37f9a8 147 BotLock timer_lock (timer_mutex);
cb21075d 148 int when = time(NULL) + delay;
3c37f9a8 149
cb21075d 150 scm_gc_protect_object(function);
3c37f9a8 151
152 Timer *timer = new Timer (++counter, when, function);
153 TimerList::iterator it = std::find_if (timers.begin (), timers.end (),
154 std::bind1st (timer_sort_p, timer));
155
156 if (it != timers.end ())
157 timers.insert (it, timer);
158 else
159 timers.push_back (timer);
160
161 return scm_from_int (counter);
162
cb21075d 163}
164
165bool
166BotInterp::DelTimer(SCM timer)
167{
3c37f9a8 168 BotLock timer_lock (timer_mutex);
cb21075d 169
3c37f9a8 170 int count = scm_to_int (timer);
171 TimerList::iterator it = timers.begin();
172 TimerList::iterator end = timers.end();
173
174 for ( ; it != end; ++it)
175 {
176 if ((*it)->count == count)
177 {
178 scm_gc_unprotect_object((*it)->function);
179 delete (*it);
180 timers.erase(it);
181
182 return true;
183 }
cb21075d 184 }
3c37f9a8 185
cb21075d 186 return false;
187}
188
189bool
190BotInterp::RunTimers(int now)
191{
3c37f9a8 192 BotLock timer_lock (timer_mutex);
cb21075d 193 struct wrapper_data wd;
46af1667 194 wd.args = scm_list_n (SCM_UNDEFINED);
cb21075d 195
3c37f9a8 196 while (!timers.empty ())
197 {
198 // Keep a stack allocated copy of the front of the timer queue
199 // just in case the timer is deleted while being executed (which
200 // is very unlikely as the only place this could occur is if the
201 // timer deleted itself)
202 Timer current_timer = *timers.front () ;
203
204 if (current_timer.when <= now)
205 {
206 wd.func = current_timer.function;
207
208 scm_internal_catch (SCM_BOOL_T,
209 (scm_t_catch_body) Interp::LazyApplyWrapper,
210 (void *)&wd,
211 (scm_t_catch_handler) Interp::EmptyHandler,
212 0);
213
214 // The timer list may have been modified by the timer
215 // callback; if it has in such a way that the first queue
216 // item has changed (adding a timer in the past) then we
217 // switch the slow path for deleting a timer
218 if (current_timer.count == timers.front()->count)
219 {
220 scm_gc_unprotect_object (current_timer.function);
221 delete timers.front ();
222 timers.pop_front ();
223 }
224 else
225 {
226 DelTimer (scm_from_int (current_timer.count));
227 }
228 }
229 else
230 {
231 break;
232 }
cb21075d 233 }
3c37f9a8 234
cb21075d 235 return true;
236}
237
238#endif