Fawkes API Fawkes Development Version
clips_env_manager.cpp
1
2/***************************************************************************
3 * clips_env_manager.cpp - CLIPS environment manager
4 *
5 * Created: Thu Aug 15 18:57:58 2013
6 * Copyright 2006-2014 Tim Niemueller [www.niemueller.de]
7 ****************************************************************************/
8
9/* This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version. A runtime exception applies to
13 * this software (see LICENSE.GPL_WRE file mentioned below for details).
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Library General Public License for more details.
19 *
20 * Read the full text in the LICENSE.GPL_WRE file in the doc directory.
21 */
22
23#include <baseapp/run.h>
24#include <logging/logger.h>
25#include <plugins/clips/aspect/clips_env_manager.h>
26#include <plugins/clips/aspect/clips_feature.h>
27#include <utils/time/time.h>
28
29#include <cstring>
30
31extern "C" {
32#include <clips/clips.h>
33}
34
35namespace fawkes {
36
37#define ROUTER_NAME "fawkeslog"
38
39/// @cond INTERNALS
40class CLIPSLogger
41{
42public:
43 CLIPSLogger(Logger *logger, const char *component = NULL)
44 {
45 logger_ = logger;
46 if (component) {
47 component_ = strdup(component);
48 } else {
49 component_ = NULL;
50 }
51 }
52
53 ~CLIPSLogger()
54 {
55 if (component_) {
56 free(component_);
57 }
58 }
59
60 void
61 log(const char *logical_name, const char *str)
62 {
63 if (strcmp(str, "\n") == 0) {
64 if (strcmp(logical_name, "debug") == 0 || strcmp(logical_name, "logdebug") == 0
65 || strcmp(logical_name, WTRACE) == 0) {
66 logger_->log_debug(component_ ? component_ : "CLIPS", "%s", buffer_.c_str());
67 } else if (strcmp(logical_name, "warn") == 0 || strcmp(logical_name, "logwarn") == 0
68 || strcmp(logical_name, WWARNING) == 0) {
69 logger_->log_warn(component_ ? component_ : "CLIPS", "%s", buffer_.c_str());
70 } else if (strcmp(logical_name, "error") == 0 || strcmp(logical_name, "logerror") == 0
71 || strcmp(logical_name, WERROR) == 0) {
72 logger_->log_error(component_ ? component_ : "CLIPS", "%s", buffer_.c_str());
73 } else if (strcmp(logical_name, WDIALOG) == 0) {
74 // ignored
75 } else {
76 logger_->log_info(component_ ? component_ : "CLIPS", "%s", buffer_.c_str());
77 }
78
79 buffer_.clear();
80 } else {
81 buffer_ += str;
82 }
83 }
84
85private:
86 Logger * logger_;
87 char * component_;
88 std::string buffer_;
89};
90
91class CLIPSContextMaintainer
92{
93public:
94 CLIPSContextMaintainer(Logger *logger, const char *log_component_name)
95 : logger(logger, log_component_name)
96 {
97 }
98
99 ~CLIPSContextMaintainer()
100 {
101 }
102
103public:
104 CLIPSLogger logger;
105};
106
107static int
108log_router_query(void *env, char *logical_name)
109{
110 if (strcmp(logical_name, "l") == 0)
111 return TRUE;
112 if (strcmp(logical_name, "info") == 0)
113 return TRUE;
114 if (strcmp(logical_name, "debug") == 0)
115 return TRUE;
116 if (strcmp(logical_name, "warn") == 0)
117 return TRUE;
118 if (strcmp(logical_name, "error") == 0)
119 return TRUE;
120 if (strcmp(logical_name, "loginfo") == 0)
121 return TRUE;
122 if (strcmp(logical_name, "logdebug") == 0)
123 return TRUE;
124 if (strcmp(logical_name, "logwarn") == 0)
125 return TRUE;
126 if (strcmp(logical_name, "logerror") == 0)
127 return TRUE;
128 if (strcmp(logical_name, "stdout") == 0)
129 return TRUE;
130 if (strcmp(logical_name, WTRACE) == 0)
131 return TRUE;
132 if (strcmp(logical_name, WDIALOG) == 0)
133 return TRUE;
134 if (strcmp(logical_name, WWARNING) == 0)
135 return TRUE;
136 if (strcmp(logical_name, WERROR) == 0)
137 return TRUE;
138 if (strcmp(logical_name, WDISPLAY) == 0)
139 return TRUE;
140 return FALSE;
141}
142
143static int
144log_router_print(void *env, char *logical_name, char *str)
145{
146 void * rc = GetEnvironmentRouterContext(env);
147 CLIPSLogger *logger = static_cast<CLIPSLogger *>(rc);
148 logger->log(logical_name, str);
149 return TRUE;
150}
151
152static int
153log_router_exit(void *env, int exit_code)
154{
155 return TRUE;
156}
157
158/// @endcond
159
160/** @class CLIPSEnvManager <plugins/clips/aspect/clips_env_manager.h>
161 * CLIPS environment manager.
162 * The CLIPS environment manager creates and maintains CLIPS
163 * environments, registers features and provides them to the CLIPS
164 * environments, and allows access to any and all CLIPS environments.
165 * @author Tim Niemueller
166 */
167
168/** Constructor.
169 * @param logger logger to log messages from created environments
170 * @param clock clock to get time from for (now)
171 * @param clips_dir path where to look for CLIPS files
172 */
173CLIPSEnvManager::CLIPSEnvManager(Logger *logger, Clock *clock, std::string &clips_dir)
174{
175 logger_ = logger;
176 clock_ = clock;
177 clips_dir_ = clips_dir;
178}
179
180/** Destructor. */
182{
183}
184
185/** Create a new environment.
186 * This function creates a new plain environment and sets up logging etc.
187 * @param log_component_name prefix for log entries
188 * @return readily initialized CLIPS environment
189 */
191CLIPSEnvManager::new_env(const std::string &log_component_name)
192{
193 // CLIPS overwrites the SIGINT handler, restore it after
194 // initializing the environment
195 struct sigaction oldact;
196 if (sigaction(SIGINT, NULL, &oldact) == 0) {
197 LockPtr<CLIPS::Environment> clips(new CLIPS::Environment(),
198 /* recursive mutex */ true);
199
200 // by default be silent
201 clips->unwatch("all");
202
203 CLIPSContextMaintainer *cm = new CLIPSContextMaintainer(logger_, log_component_name.c_str());
204
205 void *env = clips->cobj();
206
207 SetEnvironmentContext(env, cm);
208
209 EnvAddRouterWithContext(env,
210 (char *)ROUTER_NAME,
211 /* exclusive */ 30,
212 log_router_query,
213 log_router_print,
214 /* getc */ NULL,
215 /* ungetc */ NULL,
216 log_router_exit,
217 &cm->logger);
218
219 // restore old action
220 sigaction(SIGINT, &oldact, NULL);
221
222 return clips;
223 } else {
224 throw Exception("CLIPS: Unable to backup "
225 "SIGINT sigaction for restoration.");
226 }
227}
228
229/** Create a new environment.
230 * The environment is registered internally under the specified name.
231 * It must be destroyed when done with it. Only a single environment
232 * can be created for a particular environment name.
233 * @param env_name name by which to register environment
234 * @param log_component_name prefix for log entries
235 * @return readily initialized CLIPS environment
236 */
237LockPtr<CLIPS::Environment>
238CLIPSEnvManager::create_env(const std::string &env_name, const std::string &log_component_name)
239{
241 if (envs_.find(env_name) != envs_.end()) {
242 throw Exception("CLIPS environment '%s' already exists", env_name.c_str());
243 }
244
245 clips = new_env(log_component_name);
246
247 if (clips) {
248 envs_[env_name].env = clips;
249
250 // add generic functions
251 add_functions(env_name, clips);
252
253 // assert all currently available features to environment
254 assert_features(clips, true);
255
256 guarded_load(env_name, clips_dir_ + "utils.clp");
257 guarded_load(env_name, clips_dir_ + "time.clp");
258 guarded_load(env_name, clips_dir_ + "path.clp");
259
260 clips->evaluate("(path-add \"" + clips_dir_ + "\")");
261
262 return clips;
263 } else {
264 throw Exception("Failed to initialize CLIPS environment '%s'", env_name.c_str());
265 }
266}
267
268/** Destroy the named environment.
269 * Only ever destroy environments which you have created yourself.
270 * @param env_name name of the environment to destroy
271 */
272void
273CLIPSEnvManager::destroy_env(const std::string &env_name)
274{
275 if (envs_.find(env_name) != envs_.end()) {
276 void * env = envs_[env_name].env->cobj();
277 CLIPSContextMaintainer *cm = static_cast<CLIPSContextMaintainer *>(GetEnvironmentContext(env));
278
279 EnvDeleteRouter(env, (char *)ROUTER_NAME);
280 SetEnvironmentContext(env, NULL);
281 delete cm;
282
283 for (auto feat : envs_[env_name].req_feat) {
284 if (features_.find(feat) != features_.end()) {
285 features_[feat]->clips_context_destroyed(env_name);
286 }
287 }
288
289 envs_.erase(env_name);
290 }
291}
292
293/** Get map of environments.
294 * @return map from environment name to environment lock ptr
295 */
296std::map<std::string, LockPtr<CLIPS::Environment>>
298{
299 std::map<std::string, LockPtr<CLIPS::Environment>> rv;
300 for (auto envd : envs_) {
301 rv[envd.first] = envd.second.env;
302 }
303 return rv;
304}
305
306CLIPS::Value
307CLIPSEnvManager::clips_request_feature(std::string env_name, std::string feature_name)
308{
309 bool rv = true;
310
311 logger_->log_debug("ClipsEnvManager",
312 "Environment %s requests feature %s",
313 env_name.c_str(),
314 feature_name.c_str());
315
316 if (envs_.find(env_name) == envs_.end()) {
317 logger_->log_warn("ClipsEnvManager",
318 "Feature %s request from non-existent environment %s",
319 feature_name.c_str(),
320 env_name.c_str());
321 return CLIPS::Value("FALSE", CLIPS::TYPE_SYMBOL);
322 }
323 if (features_.find(feature_name) == features_.end()) {
324 logger_->log_warn("ClipsEnvManager",
325 "Environment requested unavailable feature %s",
326 feature_name.c_str());
327 return CLIPS::Value("FALSE", CLIPS::TYPE_SYMBOL);
328 }
329
330 ClipsEnvData &envd = envs_[env_name];
331 if (std::binary_search(envd.req_feat.begin(), envd.req_feat.end(), feature_name)) {
332 logger_->log_warn("ClipsEnvManager",
333 "Environment %s requested feature %s *again*",
334 env_name.c_str(),
335 feature_name.c_str());
336 return CLIPS::Value("TRUE", CLIPS::TYPE_SYMBOL);
337 }
338
339 envd.env.lock();
340 features_[feature_name]->clips_context_init(env_name, envd.env);
341 envd.req_feat.push_back(feature_name);
342 envd.req_feat.sort();
343
344 // deffact so it survives a reset
345 std::string deffacts = "(deffacts ff-features-loaded";
346
347 for (auto feat : envd.req_feat) {
348 deffacts += " (ff-feature-loaded " + feat + ")";
349 }
350 deffacts += ")";
351
352 envd.env->assert_fact_f("(ff-feature-loaded %s)", feature_name.c_str());
353
354 if (!envd.env->build(deffacts)) {
355 logger_->log_warn("ClipsEnvManager",
356 "Failed to build deffacts ff-features-loaded "
357 "for %s",
358 env_name.c_str());
359 rv = false;
360 }
361 envd.env.unlock();
362
363 return CLIPS::Value(rv ? "TRUE" : "FALSE", CLIPS::TYPE_SYMBOL);
364}
365
366CLIPS::Values
367CLIPSEnvManager::clips_now()
368{
369 CLIPS::Values rv;
370 fawkes::Time now(clock_);
371 rv.push_back(now.get_sec());
372 rv.push_back(now.get_usec());
373 return rv;
374}
375
376CLIPS::Values
377CLIPSEnvManager::clips_now_systime()
378{
379 CLIPS::Values rv;
380 fawkes::Time now;
381 now.set_clock(clock_);
382 now.stamp_systime();
383 rv.push_back(now.get_sec());
384 rv.push_back(now.get_usec());
385 return rv;
386}
387
388void
389CLIPSEnvManager::add_functions(const std::string &env_name, LockPtr<CLIPS::Environment> &clips)
390{
391 clips->add_function("ff-feature-request",
392 sigc::slot<CLIPS::Value, std::string>(
393 sigc::bind<0>(sigc::mem_fun(*this, &CLIPSEnvManager::clips_request_feature),
394 env_name)));
395 clips->add_function("now",
396 sigc::slot<CLIPS::Values>(sigc::mem_fun(*this, &CLIPSEnvManager::clips_now)));
397 clips->add_function("now-systime",
398 sigc::slot<CLIPS::Values>(
399 sigc::mem_fun(*this, &CLIPSEnvManager::clips_now_systime)));
400 clips->add_function("quit", sigc::slot<void>(sigc::mem_fun(*this, &CLIPSEnvManager::quit)));
401}
402
403void
404CLIPSEnvManager::assert_features(LockPtr<CLIPS::Environment> &clips, bool immediate_assert)
405{
406 // deffact so it survives a reset
407 std::string deffacts = "(deffacts ff-features-available";
408
409 for (auto feat : features_) {
410 deffacts += " (ff-feature " + feat.first + ")";
411 if (immediate_assert) {
412 // assert so it is immediately available
413 clips->assert_fact_f("(ff-feature %s)", feat.first.c_str());
414 }
415 }
416 deffacts += ")";
417
418 if (!clips->build(deffacts)) {
419 logger_->log_warn("ClipsEnvManager", "Failed to build deffacts ff-features-available");
420 }
421}
422
423/** Add a feature by name.
424 * @param features CLIPS feature maintainers to add
425 */
426void
427CLIPSEnvManager::add_features(const std::list<CLIPSFeature *> &features)
428{
429 for (auto feat : features) {
430 const std::string &feature_name = feat->clips_feature_name;
431
432 if (features_.find(feature_name) != features_.end()) {
433 throw Exception("Feature '%s' has already been registered", feature_name.c_str());
434 }
435
436 logger_->log_info("ClipsEnvManager", "Adding feature %s", feature_name.c_str());
437
438 features_[feature_name] = feat;
439
440 // assert fact to indicate feature availability to environments
441 for (auto env : envs_) {
442 env.second.env.lock();
443 assert_features(env.second.env, false);
444 // assert so it is immediately available
445 env.second.env->assert_fact_f("(ff-feature %s)", feature_name.c_str());
446 env.second.env.unlock();
447 }
448 }
449}
450
451/** Assert that a feature can be removed.
452 * The feature will not actually be removed, it will just be checked if this
453 * would work without problem.
454 * @param features list of features to query for removal
455 * @exception Exception thrown with a descriptive message if the feature
456 * cannot be removed because it is still in use
457 */
458void
459CLIPSEnvManager::assert_can_remove_features(const std::list<CLIPSFeature *> &features)
460{
461 for (auto feat : features) {
462 const std::string &feature_name = feat->clips_feature_name;
463
464 for (auto env : envs_) {
465 if (std::binary_search(env.second.req_feat.begin(),
466 env.second.req_feat.end(),
467 feature_name)) {
468 throw Exception("Cannot remove feature %s as environment %s depends on it",
469 feature_name.c_str(),
470 env.first.c_str());
471 }
472 }
473 }
474}
475
476/** Remove a feature by name.
477 * @param features list of features to remove
478 * @exception Exception thrown with a descriptive message if the feature
479 * cannot be removed because it is still in use
480 */
481void
482CLIPSEnvManager::remove_features(const std::list<CLIPSFeature *> &features)
483{
484 // On plugin unload this would fail because destruction
485 // of threads is forced.
486 //assert_can_remove_features(features);
487 for (auto feat : features) {
488 features_.erase(feat->clips_feature_name);
489 }
490}
491
492void
493CLIPSEnvManager::guarded_load(const std::string &env_name, const std::string &filename)
494{
495 if (envs_.find(env_name) == envs_.end()) {
496 throw Exception("guarded_load: env %s has not been registered", env_name.c_str());
497 }
498
499 LockPtr<CLIPS::Environment> &clips = envs_[env_name].env;
500
501 int load_rv = 0;
502 if ((load_rv = clips->load(filename)) != 1) {
503 if (load_rv == 0) {
504 destroy_env(env_name);
505 throw Exception("%s: cannot find %s", env_name.c_str(), filename.c_str());
506 } else {
507 destroy_env(env_name);
508 throw Exception("%s: CLIPS code error in %s", env_name.c_str(), filename.c_str());
509 }
510 }
511}
512
513void
514CLIPSEnvManager::quit()
515{
516 fawkes::runtime::quit();
517}
518
519} // end namespace fawkes
LockPtr< CLIPS::Environment > create_env(const std::string &env_name, const std::string &log_component_name)
Create a new environment.
std::map< std::string, LockPtr< CLIPS::Environment > > environments() const
Get map of environments.
virtual ~CLIPSEnvManager()
Destructor.
void destroy_env(const std::string &env_name)
Destroy the named environment.
CLIPSEnvManager(Logger *logger, Clock *clock, std::string &clips_dir)
Constructor.
void add_features(const std::list< CLIPSFeature * > &features)
Add a feature by name.
void remove_features(const std::list< CLIPSFeature * > &features)
Remove a feature by name.
void assert_can_remove_features(const std::list< CLIPSFeature * > &features)
Assert that a feature can be removed.
This is supposed to be the central clock in Fawkes.
Definition: clock.h:35
Base class for exceptions in Fawkes.
Definition: exception.h:36
Interface for logging.
Definition: logger.h:42
virtual void log_debug(const char *component, const char *format,...)=0
Log debug message.
virtual void log_warn(const char *component, const char *format,...)=0
Log warning message.
virtual void log_info(const char *component, const char *format,...)=0
Log informational message.
virtual void log(LogLevel level, const char *component, const char *format,...)
Log message of given log level.
Definition: multi.cpp:153
A class for handling time.
Definition: time.h:93
void set_clock(Clock *clock)
Set clock for this instance.
Definition: time.cpp:308
long get_usec() const
Get microseconds.
Definition: time.h:127
Time & stamp_systime()
Set this time to the current system time.
Definition: time.cpp:720
long get_sec() const
Get seconds.
Definition: time.h:117
Fawkes library namespace.