Enhance threadsafety of Hooks and improve generally
[clinton/bobotpp.git] / source / BotInterp.C
1 // BotInterp.C -*- C++ -*-
2 // Copyright (c) 1998 Etienne BERNARD
3 // Copyright (C) 2002,2005,2008 Clinton Ebadi
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
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301, USA.
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <algorithm>
25
26 #include "Utils.H"
27 #include "Bot.H"
28 #include "BotInterp.H"
29
30 #ifdef USESCRIPTS
31
32 #include <libguile.h>
33 extern "C"
34 {
35 #include <libguile/regex-posix.h>
36 }
37
38 bool
39 Hook::operator< (const Hook & h) const
40 {
41 if (priority < h.priority)
42 {
43 return true;
44 }
45 else if (priority > h.priority)
46 {
47 return false;
48 }
49 else if (fallthru && h.fallthru)
50 {
51 return false;
52 }
53 else if (fallthru && !h.fallthru)
54 {
55 return false;
56 }
57 else if (!fallthru && h.fallthru)
58 {
59 return true;
60 }
61 else
62 {
63 // NOTE: This should never be reached
64 return false;
65 }
66 }
67
68 BotInterp::BotInterp(Bot *b, String fn)
69 : bot(b), counter(0),
70 hook_mutex (true), timer_mutex (true)
71 {
72 logPort = scm_open_file (Utils::str2scm (fn),
73 Utils::str2scm ("a"));
74
75 scm_gc_protect_object(logPort);
76 }
77
78 void
79 BotInterp::Execute(String command)
80 {
81 Interp::Execute(bot, command);
82 }
83
84 void
85 BotInterp::LoadScript(String filename)
86 {
87 Interp::LoadScript(bot, filename);
88 }
89
90 SCM
91 BotInterp::ScriptLog()
92 {
93 return logPort;
94 }
95
96 namespace
97 {
98 struct HookFind
99 {
100 std::string rx;
101 std::string name;
102
103 HookFind (std::string r, std::string n)
104 : rx (r), name (n)
105 { }
106
107 bool operator() (const Hook * hook) const
108 { return hook->regex_str == rx && hook->name == name; }
109 };
110 }
111
112 bool
113 BotInterp::AddHook(int hooktype, SCM regex, SCM function, int pri, bool fall,
114 std::string name)
115 {
116 if (scm_string_p(regex) == SCM_BOOL_F)
117 return false;
118
119 BotLock hook_lock (hook_mutex);
120 std::string rx = Utils::to_upper (Utils::scm2str (regex));
121 SCM r = scm_make_regexp (regex,
122 scm_list_n (scm_variable_ref (scm_c_lookup ("regexp/icase")),
123 SCM_UNDEFINED));
124 HookFind hook_find (rx, name);
125 HookList& hook_list = hooks[hooktype];
126
127 scm_gc_protect_object(r);
128 scm_gc_protect_object(function);
129
130 HookList::iterator it = std::find_if (hook_list.begin (),
131 hook_list.end (),
132 hook_find);
133
134 if (it != hook_list.end())
135 {
136 Hook * found = *it;
137
138 scm_gc_unprotect_object(found->function);
139 scm_gc_unprotect_object (r);
140
141 found->function = function;
142 found->priority = pri;
143 found->fallthru = fall;
144
145 hook_list.erase (it);
146 Utils::push_sorted (hook_list, found, hook_sort_p);
147
148 return true;
149 }
150 else
151 {
152 Utils::push_sorted (hook_list,
153 new Hook(hooktype, rx, r, function, pri, fall, name),
154 hook_sort_p);
155
156 return true;
157 }
158 }
159
160 bool
161 BotInterp::RunHooks(int hooktype, std::string match, SCM args)
162 {
163 BotLock hook_lock (hook_mutex);
164
165 SCM result;
166 wrapper_data wd;
167 wd.args = args;
168
169 // We want to execute higher priority hooks first, so we start at
170 // the end of the list instead of the beginning
171
172 for (HookList::reverse_iterator it = hooks[hooktype].rbegin();
173 it != hooks[hooktype].rend();
174 ++it)
175 {
176 std::cerr << "Matching...\n";
177 if (scm_regexp_exec((*it)->regex, Utils::str2scm (match),
178 SCM_UNDEFINED, SCM_UNDEFINED) != SCM_BOOL_F)
179 {
180 std::cerr << " Match " << (*it)->regex_str << std::endl;
181 bool fallthru_p = (*it)->fallthru;
182 wd.func = (*it)->function;
183 result = scm_internal_catch(SCM_BOOL_T,
184 (scm_t_catch_body)
185 Interp::LazyApplyWrapper,
186 static_cast<void *> (&wd),
187 (scm_t_catch_handler) Interp::EmptyHandler, 0);
188 if (!fallthru_p)
189 break;
190 }
191 }
192
193 return true;
194 }
195
196 SCM
197 BotInterp::AddTimer(int delay, SCM function)
198 {
199 BotLock timer_lock (timer_mutex);
200 int when = time(NULL) + delay;
201
202 scm_gc_protect_object(function);
203
204 Timer *timer = new Timer (++counter, when, function);
205 Utils::push_sorted (timers, timer, timer_sort_p);
206
207 return scm_from_int (counter);
208
209 }
210
211 bool
212 BotInterp::DelTimer(SCM timer)
213 {
214 BotLock timer_lock (timer_mutex);
215
216 int count = scm_to_int (timer);
217 TimerList::iterator it = timers.begin();
218 TimerList::iterator end = timers.end();
219
220 for ( ; it != end; ++it)
221 {
222 if ((*it)->count == count)
223 {
224 scm_gc_unprotect_object((*it)->function);
225 delete (*it);
226 timers.erase(it);
227
228 return true;
229 }
230 }
231
232 return false;
233 }
234
235 bool
236 BotInterp::RunTimers(int now)
237 {
238 BotLock timer_lock (timer_mutex);
239 struct wrapper_data wd;
240 wd.args = scm_list_n (SCM_UNDEFINED);
241
242 while (!timers.empty ())
243 {
244 // Keep a stack allocated copy of the front of the timer queue
245 // just in case the timer is deleted while being executed (which
246 // is very unlikely as the only place this could occur is if the
247 // timer deleted itself)
248 Timer current_timer = *timers.front () ;
249
250 if (current_timer.when <= now)
251 {
252 wd.func = current_timer.function;
253
254 scm_internal_catch (SCM_BOOL_T,
255 (scm_t_catch_body) Interp::LazyApplyWrapper,
256 (void *)&wd,
257 (scm_t_catch_handler) Interp::EmptyHandler,
258 0);
259
260 // The timer list may have been modified by the timer
261 // callback; if it has in such a way that the first queue
262 // item has changed (adding a timer in the past) then we
263 // switch the slow path for deleting a timer
264 if (current_timer.count == timers.front()->count)
265 {
266 scm_gc_unprotect_object (current_timer.function);
267 delete timers.front ();
268 timers.pop_front ();
269 }
270 else
271 {
272 DelTimer (scm_from_int (current_timer.count));
273 }
274 }
275 else
276 {
277 break;
278 }
279 }
280
281 return true;
282 }
283
284 #endif