Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/scripting/StarLuaComponents.hpp
blob: b6c19ac76a7841849ab3ff27b49f65a396c91566 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#pragma once

#include "StarPeriodic.hpp"
#include "StarLogging.hpp"
#include "StarListener.hpp"
#include "StarWorld.hpp"
#include "StarWorldLuaBindings.hpp"

namespace Star {

STAR_EXCEPTION(LuaComponentException, LuaException);

STAR_CLASS(ScriptableThread);

// Basic lua component that can be initialized (takes and then owns a script
// context, calls the script context's init function) and uninitialized
// (releases the context, calls the context 'uninit' function).
//
// Callbacks can be added and removed whether or not the context is initialized
// or not, they will be added back during a call to init.  'root' callbacks are
// available by default as well as an ephemeral 'self' table.
//
// All script function calls (init / uninit / invoke) guard against missing
// functions.  If the function is missing, it will do nothing and return
// nothing.  If the function exists but throws an error, the error will be
// logged and the component will go into the error state.
//
// Whenever an error is set, all function calls or eval will fail until the
// error is cleared by re-initializing.
//
// If 'autoReInit' is set, Monitors Root for reloads, and if a root reload
// occurs, will automatically (on the next call to invoke) uninit and then
// re-init the script before calling invoke.  'autoReInit' defaults to true.
class LuaBaseComponent {
public:
  LuaBaseComponent();
  // The LuaBaseComponent destructor does NOT call the 'unint' entry point in
  // the script.  In order to do so, uninit() must be called manually before
  // destruction.  This is because during destruction, it is highly likely that
  // callbacks may not be valid, and highly likely that exceptions could be
  // thrown.
  virtual ~LuaBaseComponent();

  LuaBaseComponent(LuaBaseComponent const& component) = delete;
  LuaBaseComponent& operator=(LuaBaseComponent const& component) = delete;

  StringList const& scripts() const;
  void setScript(String script);
  void setScripts(StringList scripts);

  void addCallbacks(String groupName, LuaCallbacks callbacks);
  bool removeCallbacks(String const& groupName);

  // If true, component will automatically uninit and re-init when root is
  // reloaded.
  bool autoReInit() const;
  void setAutoReInit(bool autoReInit);

  // Lua components require access to a LuaRoot object to initialize /
  // uninitialize.
  void setLuaRoot(LuaRootPtr luaRoot);
  LuaRootPtr const& luaRoot();

  // init returns true on success, false if there has been an error
  // initializing the script.  LuaRoot must be set before calling or this will
  // always fail.  Calls the 'init' entry point on the script context.
  bool init();
  // uninit will uninitialize the LuaComponent if it is currently initialized.
  // This calls the 'uninit' entry point on the script context before
  // destroying the context.
  void uninit();

  bool initialized() const;

  template <typename Ret = LuaValue, typename... V>
  Maybe<Ret> invoke(String const& name, V&&... args);

  template <typename Ret = LuaValue>
  Maybe<LuaValue> eval(String const& code);

  // Returns last error, if there has been an error.  Errors can only be
  // cleared by re-initializing the context.
  Maybe<String> const& error() const;

  Maybe<LuaContext> const& context() const;
  Maybe<LuaContext>& context();

protected:
  virtual void contextSetup();
  virtual void contextShutdown();

  void setError(String error);

  // Checks the initialization state of the script, while also reloading the
  // script and clearing the error state if a root reload has occurred.
  bool checkInitialization();

private:
  LuaCallbacks makeThreadsCallbacks();
  
  StringList m_scripts;
  StringMap<LuaCallbacks> m_callbacks;
  LuaRootPtr m_luaRoot;
  TrackerListenerPtr m_reloadTracker;
  Maybe<LuaContext> m_context;
  Maybe<String> m_error;
  
  StringMap<shared_ptr<ScriptableThread>> m_threads;
  mutable RecursiveMutex m_threadLock;
};

// Wraps a basic lua component to add a persistent storage table translated
// into JSON that can be stored outside of the script context.
template <typename Base>
class LuaStorableComponent : public Base {
public:
  JsonObject getScriptStorage() const;
  void setScriptStorage(JsonObject storage);

protected:
  virtual void contextSetup() override;
  virtual void contextShutdown() override;

private:
  JsonObject m_storage;
};

// Wraps a basic lua component with an 'update' method and an embedded tick
// rate.  Every call to 'update' here will only call the internal script
// 'update' at the configured delta.  Adds a update tick controls under the
// 'script' callback table.
template <typename Base>
class LuaUpdatableComponent : public Base {
public:
  LuaUpdatableComponent();

  unsigned updateDelta() const;
  float updateDt(float dt) const;
  float updateDt() const;
  void setUpdateDelta(unsigned updateDelta);

  // Returns true if the next update will call the internal script update
  // method.
  bool updateReady() const;

  template <typename Ret = LuaValue, typename... V>
  Maybe<Ret> update(V&&... args);

private:
  Periodic m_updatePeriodic;
  mutable float m_lastDt;
};

// Wraps a basic lua component so that world callbacks are added on init, and
// removed on uninit, and sets the world LuaRoot as the LuaBaseComponent
// LuaRoot automatically.
template <typename Base>
class LuaWorldComponent : public Base {
public:
  void init(World* world);
  void uninit();

protected:
  using Base::setLuaRoot;
  using Base::init;
};

// Component for scripts which can be used as entity message handlers, provides
// a 'message' table with 'setHandler' callback to set message handlers.
template <typename Base>
class LuaMessageHandlingComponent : public Base {
public:
  LuaMessageHandlingComponent();

  Maybe<Json> handleMessage(String const& message, bool localMessage, JsonArray const& args = {});

protected:
  virtual void contextShutdown() override;

private:
  struct MessageHandler {
    Maybe<LuaFunction> function;
    String name;
    bool passName = true;
    bool localOnly = false;
  };

  StringMap<MessageHandler> m_messageHandlers;
};

template <typename Ret, typename... V>
Maybe<Ret> LuaBaseComponent::invoke(String const& name, V&&... args) {
  if (!checkInitialization())
    return {};

  try {
    auto method = m_context->getPath(name);
    if (method == LuaNil)
      return {};
    return m_context->luaTo<LuaFunction>(std::move(method)).invoke<Ret>(std::forward<V>(args)...);
  } catch (LuaException const& e) {
    Logger::error("Exception while invoking lua function '{}'. {}", name, outputException(e, true));
    setError(printException(e, false));
    return {};
  }
}

template <typename Ret>
Maybe<LuaValue> LuaBaseComponent::eval(String const& code) {
  if (!checkInitialization())
    return {};

  try {
    return m_context->eval<Ret>(code);
  } catch (LuaException const& e) {
    Logger::error("Exception while evaluating lua in context: {}", outputException(e, true));
    return {};
  }
}

template <typename Base>
JsonObject LuaStorableComponent<Base>::getScriptStorage() const {
  if (Base::initialized())
    return Base::context()->template getPath<JsonObject>("storage");
  else
    return m_storage;
}

template <typename Base>
void LuaStorableComponent<Base>::setScriptStorage(JsonObject storage) {
  if (Base::initialized())
    Base::context()->setPath("storage", std::move(storage));
  else
    m_storage = std::move(storage);
}

template <typename Base>
void LuaStorableComponent<Base>::contextSetup() {
  Base::contextSetup();
  Base::context()->setPath("storage", std::move(m_storage));
}

template <typename Base>
void LuaStorableComponent<Base>::contextShutdown() {
  m_storage = Base::context()->template getPath<JsonObject>("storage");
  Base::contextShutdown();
}

template <typename Base>
LuaUpdatableComponent<Base>::LuaUpdatableComponent() {
  m_updatePeriodic.setStepCount(1);

  LuaCallbacks scriptCallbacks;
  scriptCallbacks.registerCallback("updateDt", [this]() {
      return updateDt();
    });
  scriptCallbacks.registerCallback("setUpdateDelta", [this](unsigned d) {
      setUpdateDelta(d);
    });

  m_lastDt = GlobalTimestep * GlobalTimescale;
  Base::addCallbacks("script", std::move(scriptCallbacks));
}

template <typename Base>
unsigned LuaUpdatableComponent<Base>::updateDelta() const {
  return m_updatePeriodic.stepCount();
}

template <typename Base>
float LuaUpdatableComponent<Base>::updateDt(float dt) const {
  m_lastDt = dt;
  return m_updatePeriodic.stepCount() * dt;
}

template <typename Base>
float LuaUpdatableComponent<Base>::updateDt() const {
  return m_updatePeriodic.stepCount() * m_lastDt;
}


template <typename Base>
void LuaUpdatableComponent<Base>::setUpdateDelta(unsigned updateDelta) {
  m_updatePeriodic.setStepCount(updateDelta);
}

template <typename Base>
bool LuaUpdatableComponent<Base>::updateReady() const {
  return m_updatePeriodic.ready();
}

template <typename Base>
template <typename Ret, typename... V>
Maybe<Ret> LuaUpdatableComponent<Base>::update(V&&... args) {
  if (!m_updatePeriodic.tick())
    return {};

  return Base::template invoke<Ret>("update", std::forward<V>(args)...);
}

template <typename Base>
void LuaWorldComponent<Base>::init(World* world) {
  if (Base::initialized())
    uninit();

  Base::setLuaRoot(world->luaRoot());
  Base::addCallbacks("world", LuaBindings::makeWorldCallbacks(world));
  Base::init();
}

template <typename Base>
void LuaWorldComponent<Base>::uninit() {
  Base::uninit();
  Base::removeCallbacks("world");
}

template <typename Base>
LuaMessageHandlingComponent<Base>::LuaMessageHandlingComponent() {
  LuaCallbacks scriptCallbacks;
  scriptCallbacks.registerCallback("setHandler", [this](Variant<String, Json> message, Maybe<LuaFunction> handler) {
      MessageHandler handlerInfo = {};

      if (Json* config = message.ptr<Json>()) {
        handlerInfo.passName = config->getBool("passName", false);
        handlerInfo.localOnly = config->getBool("localOnly", false);
        handlerInfo.name = config->getString("name");
      } else {
        handlerInfo.passName = true;
        handlerInfo.localOnly = false;
        handlerInfo.name = message.get<String>();
      }

      if (handler) {
        handlerInfo.function.emplace(handler.take());
        m_messageHandlers.set(handlerInfo.name, handlerInfo);
      }
      else
        m_messageHandlers.remove(handlerInfo.name);
    });

  Base::addCallbacks("message", std::move(scriptCallbacks));
}

template <typename Base>
Maybe<Json> LuaMessageHandlingComponent<Base>::handleMessage(
    String const& message, bool localMessage, JsonArray const& args) {
  if (!Base::initialized())
    return {};

  if (auto handler = m_messageHandlers.ptr(message)) {
    try {
      if (handler->localOnly) {
        if (!localMessage)
          return {};
        else if (handler->passName)
          return handler->function->template invoke<Json>(message, luaUnpack(args));
        else
          return handler->function->template invoke<Json>(luaUnpack(args));
      }
      else if (handler->passName)
        return handler->function->template invoke<Json>(message, localMessage, luaUnpack(args));
      else
        return handler->function->template invoke<Json>(localMessage, luaUnpack(args));
    } catch (LuaException const& e) {
      Logger::error(
          "Exception while invoking lua message handler for message '{}'. {}", message, outputException(e, true));
      Base::setError(String(printException(e, false)));
    }
  }
  return {};
}

template <typename Base>
void LuaMessageHandlingComponent<Base>::contextShutdown() {
  m_messageHandlers.clear();
  Base::contextShutdown();
}
}