672 lines
19 KiB
C++
672 lines
19 KiB
C++
// http://www.apache.org/licenses/LICENSE-2.0
|
|
// Copyright 2014 Perttu Ahola <celeron55@gmail.com>
|
|
#include "app.h"
|
|
#include "core/log.h"
|
|
#include "client/config.h"
|
|
#include "client/state.h"
|
|
#include "lua_bindings/init.h"
|
|
#include "lua_bindings/util.h"
|
|
#include "lua_bindings/replicate.h"
|
|
#include "interface/fs.h"
|
|
#include "interface/voxel.h"
|
|
#include "interface/thread_pool.h"
|
|
#include <c55/getopt.h>
|
|
#include <c55/os.h>
|
|
#include <Application.h>
|
|
#include <Engine.h>
|
|
#include <LuaScript.h>
|
|
#include <CoreEvents.h>
|
|
#include <Input.h>
|
|
#include <ResourceCache.h>
|
|
#include <Graphics.h>
|
|
#include <GraphicsEvents.h> // E_SCREENMODE
|
|
#include <IOEvents.h> // E_LOGMESSAGE
|
|
#include <Log.h>
|
|
#include <DebugHud.h>
|
|
#include <XMLFile.h>
|
|
#include <Scene.h>
|
|
#include <LuaFunction.h>
|
|
#include <Viewport.h>
|
|
#include <Camera.h>
|
|
#include <Renderer.h>
|
|
#include <Octree.h>
|
|
#include <FileSystem.h>
|
|
#include <PhysicsWorld.h>
|
|
#include <DebugRenderer.h>
|
|
#include <Profiler.h>
|
|
extern "C" {
|
|
#include <lua.h>
|
|
#include <lauxlib.h>
|
|
}
|
|
#include <signal.h>
|
|
#define MODULE "__app"
|
|
namespace magic = Urho3D;
|
|
|
|
extern client::Config g_client_config;
|
|
extern bool g_sigint_received;
|
|
|
|
namespace app {
|
|
|
|
void GraphicsOptions::apply(magic::Graphics *magic_graphics)
|
|
{
|
|
int w = fullscreen ? full_w : window_w;
|
|
int h = fullscreen ? full_h : window_h;
|
|
magic_graphics->SetMode(w, h, fullscreen, borderless, resizable,
|
|
vsync, triple_buffer, multisampling);
|
|
}
|
|
|
|
class BuildatResourceRouter: public magic::ResourceRouter
|
|
{
|
|
OBJECT(BuildatResourceRouter);
|
|
|
|
sp_<client::State> m_client;
|
|
public:
|
|
BuildatResourceRouter(magic::Context *context):
|
|
magic::ResourceRouter(context)
|
|
{}
|
|
void set_client(sp_<client::State> client)
|
|
{
|
|
m_client = client;
|
|
}
|
|
|
|
void Route(magic::String &name, magic::ResourceRequest requestType)
|
|
{
|
|
if(!m_client){
|
|
log_w(MODULE, "Resource route access: %s (client not initialized)",
|
|
name.CString());
|
|
return;
|
|
}
|
|
ss_ path = m_client->get_file_path(name.CString());
|
|
if(path == ""){
|
|
log_v(MODULE, "Resource route access: %s (assuming local file)",
|
|
name.CString());
|
|
// NOTE: Path safety is checked by magic::FileSystem
|
|
return;
|
|
}
|
|
log_v(MODULE, "Resource route access: %s -> %s",
|
|
name.CString(), cs(path));
|
|
name = path.c_str();
|
|
}
|
|
};
|
|
|
|
struct CApp: public App, public magic::Application
|
|
{
|
|
sp_<client::State> m_state;
|
|
BuildatResourceRouter *m_router;
|
|
magic::LuaScript *m_script;
|
|
lua_State *L;
|
|
bool m_reboot_requested = false;
|
|
Options m_options;
|
|
bool m_draw_debug_geometry = false;
|
|
int64_t m_last_update_us;
|
|
|
|
magic::SharedPtr<magic::Scene> m_scene;
|
|
magic::SharedPtr<magic::Node> m_camera_node;
|
|
|
|
sp_<interface::thread_pool::ThreadPool> m_thread_pool;
|
|
|
|
CApp(magic::Context *context, const Options &options):
|
|
magic::Application(context),
|
|
m_script(nullptr),
|
|
L(nullptr),
|
|
m_options(options),
|
|
m_last_update_us(get_timeofday_us()),
|
|
m_thread_pool(interface::thread_pool::createThreadPool())
|
|
{
|
|
log_v(MODULE, "constructor()");
|
|
log_v(MODULE, "window size: %ix%i",
|
|
m_options.graphics.window_w, m_options.graphics.window_h);
|
|
|
|
m_thread_pool->start(4); // TODO: Configurable
|
|
|
|
sv_<ss_> resource_paths = {
|
|
g_client_config.get<ss_>("cache_path")+"/tmp",
|
|
g_client_config.get<ss_>("share_path")+"/extensions", // Could be unsafe
|
|
g_client_config.get<ss_>("urho3d_path")+"/Bin/CoreData",
|
|
g_client_config.get<ss_>("urho3d_path")+"/Bin/Data",
|
|
};
|
|
ss_ resource_paths_s;
|
|
for(const ss_ &path : resource_paths){
|
|
if(!resource_paths_s.empty())
|
|
resource_paths_s += ";";
|
|
resource_paths_s += interface::fs::get_absolute_path(path);
|
|
}
|
|
|
|
// Set allowed paths in urho3d filesystem (part of sandbox)
|
|
magic::FileSystem *magic_fs = GetSubsystem<magic::FileSystem>();
|
|
for(const ss_ &path : resource_paths){
|
|
magic_fs->RegisterPath(interface::fs::get_absolute_path(path).c_str());
|
|
}
|
|
magic_fs->RegisterPath(interface::fs::get_absolute_path(
|
|
g_client_config.get<ss_>("cache_path")).c_str());
|
|
|
|
// Useful for saving stuff for inspection when debugging
|
|
magic_fs->RegisterPath("/tmp");
|
|
|
|
// Set Urho3D engine parameters
|
|
engineParameters_["WindowTitle"] = "Buildat Client";
|
|
engineParameters_["Headless"] = false;
|
|
engineParameters_["ResourcePaths"] = resource_paths_s.c_str();
|
|
engineParameters_["AutoloadPaths"] = "";
|
|
engineParameters_["LogName"] = "";
|
|
engineParameters_["LogQuiet"] = true; // Don't log to stdout
|
|
|
|
// Graphics options
|
|
engineParameters_["FullScreen"] = m_options.graphics.fullscreen;
|
|
if(m_options.graphics.fullscreen){
|
|
engineParameters_["WindowWidth"] = m_options.graphics.full_w;
|
|
engineParameters_["WindowHeight"] = m_options.graphics.full_h;
|
|
} else {
|
|
engineParameters_["WindowWidth"] = m_options.graphics.window_w;
|
|
engineParameters_["WindowHeight"] = m_options.graphics.window_h;
|
|
engineParameters_["WindowResizable"] = m_options.graphics.resizable;
|
|
}
|
|
engineParameters_["VSync"] = m_options.graphics.vsync;
|
|
engineParameters_["TripleBuffer"] = m_options.graphics.triple_buffer;
|
|
engineParameters_["Multisample"] = m_options.graphics.multisampling;
|
|
|
|
magic::Log *magic_log = GetSubsystem<magic::Log>();
|
|
// Disable timestamps in log messages (also added to events)
|
|
magic_log->SetTimeStamp(false);
|
|
|
|
// Set up event handlers
|
|
SubscribeToEvent(magic::E_UPDATE, HANDLER(CApp, on_update));
|
|
SubscribeToEvent(magic::E_POSTRENDERUPDATE,
|
|
HANDLER(CApp, on_post_render_update));
|
|
SubscribeToEvent(magic::E_KEYDOWN, HANDLER(CApp, on_keydown));
|
|
SubscribeToEvent(magic::E_SCREENMODE, HANDLER(CApp, on_screenmode));
|
|
SubscribeToEvent(magic::E_LOGMESSAGE, HANDLER(CApp, on_logmessage));
|
|
|
|
// Default to not grabbing the mouse
|
|
magic::Input *magic_input = GetSubsystem<magic::Input>();
|
|
magic_input->SetMouseVisible(true);
|
|
|
|
// Default to auto-loading resources as they are modified
|
|
magic::ResourceCache *magic_cache = GetSubsystem<magic::ResourceCache>();
|
|
magic_cache->SetAutoReloadResources(true);
|
|
m_router = new BuildatResourceRouter(context_);
|
|
magic_cache->SetResourceRouter(m_router);
|
|
}
|
|
|
|
~CApp()
|
|
{
|
|
}
|
|
|
|
void set_state(sp_<client::State> state)
|
|
{
|
|
m_state = state;
|
|
m_router->set_client(state);
|
|
}
|
|
|
|
int run()
|
|
{
|
|
return magic::Application::Run();
|
|
}
|
|
|
|
void shutdown()
|
|
{
|
|
log_v(MODULE, "shutdown()");
|
|
|
|
// Save window position
|
|
magic::Graphics *magic_graphics = GetSubsystem<magic::Graphics>();
|
|
magic::IntVector2 v = magic_graphics->GetWindowPosition();
|
|
m_options.graphics.window_x = v.x_;
|
|
m_options.graphics.window_y = v.y_;
|
|
|
|
magic::Engine *engine = GetSubsystem<magic::Engine>();
|
|
engine->Exit();
|
|
}
|
|
|
|
bool reboot_requested()
|
|
{
|
|
return m_reboot_requested;
|
|
}
|
|
|
|
Options get_current_options()
|
|
{
|
|
return m_options;
|
|
}
|
|
|
|
void run_script(const ss_ &script)
|
|
{
|
|
log_v(MODULE, "run_script():\n%s", cs(script));
|
|
|
|
lua_getfield(L, LUA_GLOBALSINDEX, "__buildat_run_code_in_sandbox");
|
|
lua_pushlstring(L, script.c_str(), script.size());
|
|
error_logging_pcall(L, 1, 1);
|
|
bool status = lua_toboolean(L, -1);
|
|
lua_pop(L, 1);
|
|
if(status == false){
|
|
log_w(MODULE, "run_script(): failed");
|
|
} else {
|
|
log_v(MODULE, "run_script(): succeeded");
|
|
}
|
|
}
|
|
|
|
bool run_script_no_sandbox(const ss_ &script)
|
|
{
|
|
log_v(MODULE, "run_script_no_sandbox():\n%s", cs(script));
|
|
|
|
// TODO: Use lua_load() so that chunkname can be set
|
|
if(luaL_loadstring(L, script.c_str())){
|
|
ss_ error = lua_bindings::lua_tocppstring(L, -1);
|
|
log_e("%s", cs(error));
|
|
lua_pop(L, 1);
|
|
return false;
|
|
}
|
|
error_logging_pcall(L, 0, 0);
|
|
return true;
|
|
}
|
|
|
|
void handle_packet(const ss_ &name, const ss_ &data)
|
|
{
|
|
log_v(MODULE, "handle_packet(): %s", cs(name));
|
|
|
|
lua_getfield(L, LUA_GLOBALSINDEX, "__buildat_handle_packet");
|
|
lua_pushlstring(L, name.c_str(), name.size());
|
|
lua_pushlstring(L, data.c_str(), data.size());
|
|
error_logging_pcall(L, 2, 0);
|
|
}
|
|
|
|
void file_updated_in_cache(const ss_ &file_name,
|
|
const ss_ &file_hash, const ss_ &cached_path)
|
|
{
|
|
log_v(MODULE, "file_updated_in_cache(): %s", cs(file_name));
|
|
|
|
magic::ResourceCache *magic_cache = GetSubsystem<magic::ResourceCache>();
|
|
magic_cache->ReloadResourceWithDependencies(file_name.c_str());
|
|
|
|
/*lua_getfield(L, LUA_GLOBALSINDEX, "__buildat_file_updated_in_cache");
|
|
if(lua_isnil(L, -1)){
|
|
lua_pop(L, 1);
|
|
return;
|
|
}
|
|
lua_pushlstring(L, file_name.c_str(), file_name.size());
|
|
lua_pushlstring(L, file_hash.c_str(), file_hash.size());
|
|
lua_pushlstring(L, cached_path.c_str(), cached_path.size());
|
|
error_logging_pcall(L, 3, 0);*/
|
|
}
|
|
|
|
magic::Scene* get_scene()
|
|
{
|
|
return m_scene;
|
|
}
|
|
|
|
interface::thread_pool::ThreadPool* get_thread_pool()
|
|
{
|
|
return m_thread_pool.get();
|
|
}
|
|
|
|
lua_State* get_lua()
|
|
{
|
|
return L;
|
|
}
|
|
|
|
// Non-public methods
|
|
|
|
void Start()
|
|
{
|
|
log_v(MODULE, "Start()");
|
|
|
|
// Restore window to previous position
|
|
if(m_options.graphics.window_x != GraphicsOptions::UNDEFINED_INT &&
|
|
m_options.graphics.window_y != GraphicsOptions::UNDEFINED_INT){
|
|
magic::Graphics *magic_graphics = GetSubsystem<magic::Graphics>();
|
|
magic_graphics->SetWindowPosition(
|
|
m_options.graphics.window_x, m_options.graphics.window_y);
|
|
}
|
|
|
|
// Instantiate and register the Lua script subsystem so that we can use the LuaScriptInstance component
|
|
context_->RegisterSubsystem(new magic::LuaScript(context_));
|
|
|
|
m_script = context_->GetSubsystem<magic::LuaScript>();
|
|
L = m_script->GetState();
|
|
if(L == nullptr)
|
|
throw Exception("m_script.GetState() returned null");
|
|
|
|
// Store current CApp instance in registry
|
|
lua_pushlightuserdata(L, (void*)this);
|
|
lua_setfield(L, LUA_REGISTRYINDEX, "__buildat_app");
|
|
|
|
lua_bindings::init(L);
|
|
|
|
#define DEF_BUILDAT_FUNC(name){ \
|
|
lua_pushcfunction(L, l_##name); \
|
|
lua_setglobal(L, "__buildat_" #name); \
|
|
}
|
|
|
|
DEF_BUILDAT_FUNC(connect_server)
|
|
DEF_BUILDAT_FUNC(disconnect)
|
|
DEF_BUILDAT_FUNC(send_packet);
|
|
DEF_BUILDAT_FUNC(get_file_path)
|
|
DEF_BUILDAT_FUNC(get_file_content)
|
|
DEF_BUILDAT_FUNC(get_path)
|
|
DEF_BUILDAT_FUNC(extension_path)
|
|
|
|
// Create a scene that will be synchronized from the server
|
|
m_scene = new magic::Scene(context_);
|
|
m_scene->CreateComponent<magic::Octree>(magic::LOCAL);
|
|
m_scene->CreateComponent<magic::PhysicsWorld>(magic::LOCAL);
|
|
m_scene->CreateComponent<magic::DebugRenderer>(magic::LOCAL);
|
|
|
|
// Push the scene to the Lua environment
|
|
lua_bindings::replicate::set_scene(L, m_scene);
|
|
|
|
// Run initial client Lua scripts
|
|
ss_ init_lua_path = g_client_config.get<ss_>("share_path")+
|
|
"/client/init.lua";
|
|
int error = luaL_dofile(L, init_lua_path.c_str());
|
|
if(error){
|
|
log_w(MODULE, "luaL_dofile: An error occurred: %s\n",
|
|
lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
throw AppStartupError("Could not initialize Lua environment");
|
|
}
|
|
|
|
// Launch menu if requested
|
|
if(g_client_config.get<bool>("boot_to_menu")){
|
|
ss_ extname = g_client_config.get<ss_>("menu_extension_name");
|
|
ss_ script = ss_() +
|
|
"local m = require('buildat/extension/"+extname+"')\n"
|
|
"if type(m) ~= 'table' then\n"
|
|
" error('Failed to load extension "+extname+"')\n"
|
|
"end\n"
|
|
"m.boot()\n";
|
|
if(!run_script_no_sandbox(script)){
|
|
throw AppStartupError(ss_()+
|
|
"Failed to load and run extension "+extname);
|
|
}
|
|
}
|
|
|
|
// Create debug HUD
|
|
magic::ResourceCache *magic_cache = GetSubsystem<magic::ResourceCache>();
|
|
magic::DebugHud *dhud = GetSubsystem<magic::Engine>()->CreateDebugHud();
|
|
dhud->SetDefaultStyle(magic_cache->GetResource<magic::XMLFile>(
|
|
"UI/DefaultStyle.xml"));
|
|
}
|
|
|
|
void on_update(magic::StringHash event_type, magic::VariantMap &event_data)
|
|
{
|
|
/*magic::AutoProfileBlock profiler_block(
|
|
GetSubsystem<magic::Profiler>(), "App::on_update");*/
|
|
|
|
if(g_sigint_received)
|
|
shutdown();
|
|
if(m_state)
|
|
m_state->update();
|
|
|
|
{
|
|
magic::AutoProfileBlock profiler_block(
|
|
GetSubsystem<magic::Profiler>(), "Buildat|ThreadPool::post");
|
|
m_thread_pool->run_post();
|
|
}
|
|
|
|
#ifdef DEBUG_CORE_TIMING
|
|
int64_t t1 = get_timeofday_us();
|
|
int interval = t1 - m_last_update_us;
|
|
if(interval > 30000)
|
|
log_w(MODULE, "Too long update interval: %ius", interval);
|
|
m_last_update_us = t1;
|
|
#endif
|
|
}
|
|
|
|
void on_post_render_update(
|
|
magic::StringHash event_type, magic::VariantMap &event_data)
|
|
{
|
|
if(m_draw_debug_geometry)
|
|
m_scene->GetComponent<magic::PhysicsWorld>()->DrawDebugGeometry(true);
|
|
}
|
|
|
|
void on_keydown(magic::StringHash event_type, magic::VariantMap &event_data)
|
|
{
|
|
int key = event_data["Key"].GetInt();
|
|
if(key == Urho3D::KEY_F11){
|
|
log_v(MODULE, "F11");
|
|
magic::Graphics *magic_graphics = GetSubsystem<magic::Graphics>();
|
|
if(magic_graphics->GetFullscreen()){
|
|
// Switch to windowed mode
|
|
magic::Graphics *magic_graphics = GetSubsystem<magic::Graphics>();
|
|
m_options.graphics.fullscreen = false;
|
|
m_options.graphics.resizable = true;
|
|
m_options.graphics.apply(magic_graphics);
|
|
} else {
|
|
// Switch to fullscreen mode
|
|
magic::Graphics *magic_graphics = GetSubsystem<magic::Graphics>();
|
|
m_options.graphics.fullscreen = true;
|
|
m_options.graphics.resizable = false;
|
|
m_options.graphics.apply(magic_graphics);
|
|
}
|
|
}
|
|
if(key == Urho3D::KEY_F10){
|
|
ss_ extname = "sandbox_test";
|
|
ss_ script = ss_() +
|
|
"local m = require('buildat/extension/"+extname+"')\n"
|
|
"if type(m) ~= 'table' then\n"
|
|
" error('Failed to load extension "+extname+"')\n"
|
|
"end\n"
|
|
"m.toggle()\n";
|
|
if(!run_script_no_sandbox(script)){
|
|
log_e(MODULE, "Failed to load and run extension %s", cs(extname));
|
|
}
|
|
}
|
|
if(key == Urho3D::KEY_F9){
|
|
magic::DebugHud *dhud = GetSubsystem<magic::Engine>()->CreateDebugHud();
|
|
dhud->ToggleAll();
|
|
}
|
|
if(key == Urho3D::KEY_F8){
|
|
m_draw_debug_geometry = !m_draw_debug_geometry;
|
|
}
|
|
}
|
|
|
|
void on_screenmode(magic::StringHash event_type, magic::VariantMap &event_data)
|
|
{
|
|
// If in windowed mode, update resolution in options
|
|
magic::Graphics *magic_graphics = GetSubsystem<magic::Graphics>();
|
|
if(!magic_graphics->GetFullscreen()){
|
|
m_options.graphics.window_w = event_data["Width"].GetInt();
|
|
m_options.graphics.window_h = event_data["Height"].GetInt();
|
|
log_v(MODULE, "Window size in graphics options updated: %ix%i",
|
|
m_options.graphics.window_w, m_options.graphics.window_h);
|
|
}
|
|
}
|
|
|
|
void on_logmessage(magic::StringHash event_type, magic::VariantMap &event_data)
|
|
{
|
|
int magic_level = event_data["Level"].GetInt();
|
|
ss_ message = event_data["Message"].GetString().CString();
|
|
//log_v(MODULE, "on_logmessage(): %i, %s", magic_level, cs(message));
|
|
int c55_level = CORE_ERROR;
|
|
if(magic_level == magic::LOG_DEBUG)
|
|
c55_level = CORE_DEBUG;
|
|
else if(magic_level == magic::LOG_INFO)
|
|
c55_level = CORE_VERBOSE;
|
|
else if(magic_level == magic::LOG_WARNING)
|
|
c55_level = CORE_WARNING;
|
|
else if(magic_level == magic::LOG_ERROR)
|
|
c55_level = CORE_ERROR;
|
|
log_(c55_level, MODULE, "Urho3D %s", cs(message));
|
|
}
|
|
|
|
// Apps-specific lua functions
|
|
|
|
// connect_server(address: string) -> status: bool, error: string or nil
|
|
static int l_connect_server(lua_State *L)
|
|
{
|
|
lua_getfield(L, LUA_REGISTRYINDEX, "__buildat_app");
|
|
CApp *self = (CApp*)lua_touserdata(L, -1);
|
|
lua_pop(L, 1);
|
|
|
|
ss_ address = lua_bindings::lua_tocppstring(L, 1);
|
|
|
|
ss_ error;
|
|
bool ok = self->m_state->connect(address, &error);
|
|
lua_pushboolean(L, ok);
|
|
if(ok)
|
|
lua_pushnil(L);
|
|
else
|
|
lua_pushstring(L, error.c_str());
|
|
return 2;
|
|
}
|
|
|
|
// disconnect()
|
|
static int l_disconnect(lua_State *L)
|
|
{
|
|
lua_getfield(L, LUA_REGISTRYINDEX, "__buildat_app");
|
|
CApp *self = (CApp*)lua_touserdata(L, -1);
|
|
lua_pop(L, 1);
|
|
|
|
if(g_client_config.get<bool>("boot_to_menu")){
|
|
// If menu, reboot client into menu
|
|
self->m_reboot_requested = true;
|
|
self->shutdown();
|
|
} else {
|
|
// If no menu, shutdown client
|
|
self->shutdown();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// send_packet(name: string, data: string)
|
|
static int l_send_packet(lua_State *L)
|
|
{
|
|
ss_ name = lua_bindings::lua_tocppstring(L, 1);
|
|
ss_ data = lua_bindings::lua_tocppstring(L, 2);
|
|
|
|
lua_getfield(L, LUA_REGISTRYINDEX, "__buildat_app");
|
|
CApp *self = (CApp*)lua_touserdata(L, -1);
|
|
lua_pop(L, 1);
|
|
|
|
try {
|
|
self->m_state->send_packet(name, data);
|
|
return 0;
|
|
} catch(std::exception &e){
|
|
log_w(MODULE, "Exception in send_packet: %s", e.what());
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// get_file_path(name: string) -> path, hash
|
|
static int l_get_file_path(lua_State *L)
|
|
{
|
|
ss_ name = lua_bindings::lua_tocppstring(L, 1);
|
|
|
|
lua_getfield(L, LUA_REGISTRYINDEX, "__buildat_app");
|
|
CApp *self = (CApp*)lua_touserdata(L, -1);
|
|
lua_pop(L, 1);
|
|
|
|
ss_ hash;
|
|
ss_ path = self->m_state->get_file_path(name, &hash);
|
|
if(path == "")
|
|
return 0;
|
|
lua_pushlstring(L, path.c_str(), path.size());
|
|
lua_pushlstring(L, hash.c_str(), hash.size());
|
|
return 2;
|
|
}
|
|
|
|
// get_file_content(name: string)
|
|
static int l_get_file_content(lua_State *L)
|
|
{
|
|
ss_ name = lua_bindings::lua_tocppstring(L, 1);
|
|
|
|
lua_getfield(L, LUA_REGISTRYINDEX, "__buildat_app");
|
|
CApp *self = (CApp*)lua_touserdata(L, -1);
|
|
lua_pop(L, 1);
|
|
|
|
try {
|
|
ss_ content = self->m_state->get_file_content(name);
|
|
lua_pushlstring(L, content.c_str(), content.size());
|
|
return 1;
|
|
} catch(std::exception &e){
|
|
log_w(MODULE, "Exception in get_file_content: %s", e.what());
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// When calling Lua from C++, this is universally good
|
|
static void error_logging_pcall(lua_State *L, int nargs, int nresults)
|
|
{
|
|
log_t(MODULE, "error_logging_pcall(): nargs=%i, nresults=%i",
|
|
nargs, nresults);
|
|
//log_d(MODULE, "stack 1: %s", cs(dump_stack(L)));
|
|
int start_L = lua_gettop(L);
|
|
lua_pushcfunction(L, lua_bindings::handle_error);
|
|
lua_insert(L, start_L - nargs);
|
|
int handle_error_L = start_L - nargs;
|
|
//log_d(MODULE, "stack 2: %s", cs(dump_stack(L)));
|
|
int r = lua_pcall(L, nargs, nresults, handle_error_L);
|
|
lua_remove(L, handle_error_L);
|
|
//log_d(MODULE, "stack 3: %s", cs(dump_stack(L)));
|
|
if(r != 0){
|
|
ss_ traceback = lua_bindings::lua_tocppstring(L, -1);
|
|
lua_pop(L, 1);
|
|
const char *msg =
|
|
r == LUA_ERRRUN ? "runtime error" :
|
|
r == LUA_ERRMEM ? "ran out of memory" :
|
|
r == LUA_ERRERR ? "error handler failed" : "unknown error";
|
|
//log_e(MODULE, "Lua %s: %s", msg, cs(traceback));
|
|
throw Exception(ss_()+"Lua "+msg+":\n"+traceback);
|
|
}
|
|
//log_d(MODULE, "stack 4: %s", cs(dump_stack(L)));
|
|
}
|
|
|
|
static void call_global_if_exists(lua_State *L,
|
|
const char *global_name, int nargs, int nresults)
|
|
{
|
|
log_t(MODULE, "call_global_if_exists(): \"%s\"", global_name);
|
|
//log_d(MODULE, "stack 1: %s", cs(dump_stack(L)));
|
|
int start_L = lua_gettop(L);
|
|
lua_getfield(L, LUA_GLOBALSINDEX, global_name);
|
|
if(lua_isnil(L, -1)){
|
|
lua_pop(L, 1 + nargs);
|
|
return;
|
|
}
|
|
lua_insert(L, start_L - nargs + 1);
|
|
error_logging_pcall(L, nargs, nresults);
|
|
//log_d(MODULE, "stack 2: %s", cs(dump_stack(L)));
|
|
}
|
|
|
|
// get_path(name: string)
|
|
static int l_get_path(lua_State *L)
|
|
{
|
|
ss_ name = lua_bindings::lua_tocppstring(L, 1);
|
|
|
|
if(name == "share"){
|
|
ss_ path = g_client_config.get<ss_>("share_path");
|
|
lua_pushlstring(L, path.c_str(), path.size());
|
|
return 1;
|
|
}
|
|
if(name == "cache"){
|
|
ss_ path = g_client_config.get<ss_>("cache_path");
|
|
lua_pushlstring(L, path.c_str(), path.size());
|
|
return 1;
|
|
}
|
|
if(name == "tmp"){
|
|
ss_ path = g_client_config.get<ss_>("cache_path")+"/tmp";
|
|
lua_pushlstring(L, path.c_str(), path.size());
|
|
return 1;
|
|
}
|
|
log_w(MODULE, "Unknown named path: \"%s\"", cs(name));
|
|
return 0;
|
|
}
|
|
|
|
// extension_path(name: string)
|
|
static int l_extension_path(lua_State *L)
|
|
{
|
|
ss_ name = lua_bindings::lua_tocppstring(L, 1);
|
|
ss_ path = g_client_config.get<ss_>("share_path")+"/extensions/"+name;
|
|
// TODO: Check if extension actually exists and do something suitable if
|
|
// not
|
|
lua_pushlstring(L, path.c_str(), path.size());
|
|
return 1;
|
|
}
|
|
};
|
|
|
|
App* createApp(magic::Context *context, const Options &options)
|
|
{
|
|
return new CApp(context, options);
|
|
}
|
|
|
|
}
|
|
// vim: set noet ts=4 sw=4:
|