1// SuperTux
2// Copyright (C) 2018 Ingo Ruhnke <grumbel@gmail.com>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "squirrel/squirrel_environment.hpp"
18
19#include <algorithm>
20
21#include "squirrel/script_interface.hpp"
22#include "squirrel/squirrel_error.hpp"
23#include "squirrel/squirrel_scheduler.hpp"
24#include "squirrel/squirrel_util.hpp"
25#include "squirrel/squirrel_virtual_machine.hpp"
26#include "supertux/game_object.hpp"
27#include "supertux/globals.hpp"
28#include "util/log.hpp"
29
30SquirrelEnvironment::SquirrelEnvironment(SquirrelVM& vm, const std::string& name) :
31 m_vm(vm),
32 m_table(),
33 m_name(name),
34 m_scripts(),
35 m_scheduler(std::make_unique<SquirrelScheduler>(m_vm))
36{
37 // garbage collector has to be invoked manually
38 sq_collectgarbage(m_vm.get_vm());
39
40 sq_newtable(m_vm.get_vm());
41 sq_pushroottable(m_vm.get_vm());
42 if (SQ_FAILED(sq_setdelegate(m_vm.get_vm(), -2)))
43 throw SquirrelError(m_vm.get_vm(), "Couldn't set table delegate");
44
45 sq_resetobject(&m_table);
46 if (SQ_FAILED(sq_getstackobj(m_vm.get_vm(), -1, &m_table))) {
47 throw SquirrelError(m_vm.get_vm(), "Couldn't get table");
48 }
49
50 sq_addref(m_vm.get_vm(), &m_table);
51 sq_pop(m_vm.get_vm(), 1);
52}
53
54SquirrelEnvironment::~SquirrelEnvironment()
55{
56 for (auto& script: m_scripts)
57 {
58 sq_release(m_vm.get_vm(), &script);
59 }
60 m_scripts.clear();
61 sq_release(m_vm.get_vm(), &m_table);
62
63 sq_collectgarbage(m_vm.get_vm());
64}
65
66void
67SquirrelEnvironment::expose_self()
68{
69 sq_pushroottable(m_vm.get_vm());
70 m_vm.store_object(m_name.c_str(), m_table);
71 sq_pop(m_vm.get_vm(), 1);
72}
73
74void
75SquirrelEnvironment::unexpose_self()
76{
77 sq_pushroottable(m_vm.get_vm());
78 m_vm.delete_table_entry(m_name.c_str());
79 sq_pop(m_vm.get_vm(), 1);
80}
81
82void
83SquirrelEnvironment::try_expose(GameObject& object)
84{
85 auto script_object = dynamic_cast<ScriptInterface*>(&object);
86 if (script_object != nullptr) {
87 sq_pushobject(m_vm.get_vm(), m_table);
88 script_object->expose(m_vm.get_vm(), -1);
89 sq_pop(m_vm.get_vm(), 1);
90 }
91}
92
93void
94SquirrelEnvironment::try_unexpose(GameObject& object)
95{
96 auto script_object = dynamic_cast<ScriptInterface*>(&object);
97 if (script_object != nullptr) {
98 SQInteger oldtop = sq_gettop(m_vm.get_vm());
99 sq_pushobject(m_vm.get_vm(), m_table);
100 try {
101 script_object->unexpose(m_vm.get_vm(), -1);
102 } catch(std::exception& e) {
103 log_warning << "Couldn't unregister object: " << e.what() << std::endl;
104 }
105 sq_settop(m_vm.get_vm(), oldtop);
106 }
107}
108
109void
110SquirrelEnvironment::unexpose(const std::string& name)
111{
112 SQInteger oldtop = sq_gettop(m_vm.get_vm());
113 sq_pushobject(m_vm.get_vm(), m_table);
114 try {
115 unexpose_object(m_vm.get_vm(), -1, name.c_str());
116 } catch(std::exception& e) {
117 log_warning << "Couldn't unregister object: " << e.what() << std::endl;
118 }
119 sq_settop(m_vm.get_vm(), oldtop);
120}
121
122void
123SquirrelEnvironment::run_script(const std::string& script, const std::string& sourcename)
124{
125 if (script.empty()) return;
126
127 std::istringstream stream(script);
128 run_script(stream, sourcename);
129}
130
131void
132SquirrelEnvironment::garbage_collect()
133{
134 m_scripts.erase(
135 std::remove_if(m_scripts.begin(), m_scripts.end(),
136 [this](HSQOBJECT& object){
137 HSQUIRRELVM vm = object_to_vm(object);
138
139 if (sq_getvmstate(vm) != SQ_VMSTATE_SUSPENDED) {
140 sq_release(m_vm.get_vm(), &object);
141 return true;
142 } else {
143 return false;
144 }
145 }),
146 m_scripts.end());
147}
148
149void
150SquirrelEnvironment::run_script(std::istream& in, const std::string& sourcename)
151{
152 garbage_collect();
153
154 try
155 {
156 HSQOBJECT object = m_vm.create_thread();
157 m_scripts.push_back(object);
158
159 HSQUIRRELVM vm = object_to_vm(object);
160
161 sq_setforeignptr(vm, this);
162
163 // set root table
164 sq_pushobject(vm, m_table);
165 sq_setroottable(vm);
166
167 compile_and_run(vm, in, sourcename);
168 }
169 catch(const std::exception& e)
170 {
171 log_warning << "Error running script: " << e.what() << std::endl;
172 }
173}
174
175void
176SquirrelEnvironment::wait_for_seconds(HSQUIRRELVM vm, float seconds)
177{
178 m_scheduler->schedule_thread(vm, g_game_time + seconds);
179}
180
181void
182SquirrelEnvironment::update(float dt_sec)
183{
184 m_scheduler->update(g_game_time);
185}
186
187/* EOF */
188