Compare commits

...

12 Commits

29 changed files with 415 additions and 294 deletions

1
.gitattributes vendored
View File

@@ -13,6 +13,7 @@
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.sln filter=lfs diff=lfs merge=lfs -text
*.uasset filter=lfs diff=lfs merge=lfs -text
*.umap filter=lfs diff=lfs merge=lfs -text

View File

@@ -1,17 +0,0 @@
[/Script/UnrealEd.EditorStyleSettings]
AssetEditorOpenLocation=MainWindow
[/Script/BlueprintGraph.BlueprintEditorSettings]
SaveOnCompile=SoC_SuccessOnly
[/Script/UnrealEd.EditorLoadingSavingSettings]
RestoreOpenAssetTabsOnRestart=AlwaysRestore
AutoSaveWarningInSeconds=0
[/Script/Integration.lxProjectSettings]
ActiveServer=/Game/Luprex/KnownServers/SS_Localhost.SS_Localhost

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -2,6 +2,7 @@
#include "BreakToDebugger.h"
#include "Blueprint/BlueprintExceptionInfo.h"
#include "Kismet2/KismetDebugUtilities.h"
#include "LuprexEditorSettings.h"
ELogVerbosity::Type FlxBreakToDebuggerOutputDevice::ConvertThreshold(ElxBreakToDebuggerThreshold Verbosity) {
switch (Verbosity) {
@@ -15,8 +16,8 @@ ELogVerbosity::Type FlxBreakToDebuggerOutputDevice::ConvertThreshold(ElxBreakToD
}
}
FlxBreakToDebuggerOutputDevice::FlxBreakToDebuggerOutputDevice(const ElxBreakToDebuggerThreshold &SensitivityRef)
: Sensitivity(SensitivityRef)
FlxBreakToDebuggerOutputDevice::FlxBreakToDebuggerOutputDevice()
: Settings(GetDefault<UlxEditorSettings>())
{
GLog->AddOutputDevice(this);
}
@@ -48,7 +49,7 @@ void FlxBreakToDebuggerOutputDevice::Serialize(const TCHAR* V, ELogVerbosity::Ty
{
// If the error isn't serious enough, do nothing.
//
if (Verbosity > ConvertThreshold(Sensitivity))
if (Verbosity > ConvertThreshold(Settings->BreakToDebuggerLogVerbosity))
{
return;
}
@@ -67,6 +68,13 @@ void FlxBreakToDebuggerOutputDevice::Serialize(const TCHAR* V, ELogVerbosity::Ty
UObject *TopObject = Frame->Object;
if (TopObject == nullptr) return;
// If the category is in the exclude list, do nothing.
//
if (Settings->BreakToDebuggerExcludeCategories.Contains(Category))
{
return;
}
// Notify the debugger that there's been an exception.
//
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::Breakpoint, FText::FromStringView(FStringView(V)));

View File

@@ -79,19 +79,15 @@ enum class ElxBreakToDebuggerThreshold : uint8 {
};
class UlxEditorSettings;
struct FlxBreakToDebuggerOutputDevice : public FOutputDevice
{
public:
// The constructor and destructor automatically register
// this output device with GLog.
//
// This struct doesn't store the sensitivity threshold.
// It relies on the LuprexGameMode class to do that, so
// that the threshold can be easily edited with the
// blueprint editor. This struct must be initialized
// with a reference to the threshold variable.
//
FlxBreakToDebuggerOutputDevice(const ElxBreakToDebuggerThreshold &SensitivityRef);
FlxBreakToDebuggerOutputDevice();
~FlxBreakToDebuggerOutputDevice();
// Inspect a log message.
@@ -109,5 +105,5 @@ public:
private:
static ELogVerbosity::Type ConvertThreshold(ElxBreakToDebuggerThreshold Verbosity);
const ElxBreakToDebuggerThreshold &Sensitivity;
const UlxEditorSettings *Settings = nullptr;
};

View File

@@ -2,3 +2,4 @@
DEFINE_LOG_CATEGORY(LogLuprex);
DEFINE_LOG_CATEGORY(LogLuprexIntegration);
DEFINE_LOG_CATEGORY(LogLuprexConsole);

View File

@@ -153,3 +153,7 @@ DECLARE_LOG_CATEGORY_EXTERN(LogLuprex, Display, All);
// Messages about the Luprex integration with Unreal.
//
DECLARE_LOG_CATEGORY_EXTERN(LogLuprexIntegration, Display, All);
// Messages from the Luprex console.
//
DECLARE_LOG_CATEGORY_EXTERN(LogLuprexConsole, Display, All);

View File

@@ -0,0 +1,7 @@
#include "LuprexEditorSettings.h"
#include "Common.h"
UlxEditorSettings::UlxEditorSettings()
{
BreakToDebuggerExcludeCategories.Add(LogLuprexConsole.GetCategoryName());
}

View File

@@ -3,7 +3,8 @@
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Engine/DeveloperSettings.h"
#include "ProjectSettings.generated.h"
#include "BreakToDebugger.h"
#include "LuprexEditorSettings.generated.h"
UCLASS(BlueprintType)
class INTEGRATION_API UlxServerSelection : public UPrimaryDataAsset
@@ -18,17 +19,31 @@ public:
FString Host;
};
UCLASS(Config=EditorPerProjectUserSettings, DefaultConfig, meta=(DisplayName="Server Settings"))
class INTEGRATION_API UlxProjectSettings : public UDeveloperSettings
UCLASS(Config=EditorPerProjectUserSettings, DefaultConfig, meta=(DisplayName="Luprex Editor Settings"))
class INTEGRATION_API UlxEditorSettings : public UDeveloperSettings
{
GENERATED_BODY()
public:
UlxEditorSettings();
virtual FName GetContainerName() const override { return TEXT("Editor"); }
virtual FName GetCategoryName() const override { return TEXT("Luprex"); }
virtual FName GetSectionName() const override { return TEXT("Server"); }
virtual FName GetSectionName() const override { return TEXT("Settings"); }
// The server to connect to. You can add an option to the list by creating
// a new data asset of type 'lx Server Selection'.
UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category="Server",
meta=(AllowedClasses="/Script/Integration.lxServerSelection"))
TSoftObjectPtr<UlxServerSelection> ActiveServer;
// If blueprint execution generates a log message of at least the following
// severity, the blueprint debugger will automatically pause with a breakpoint.
UPROPERTY(Config, EditAnywhere, Category="Debugging Tools")
ElxBreakToDebuggerThreshold BreakToDebuggerLogVerbosity;
// Log categories in this set are excluded from BreakToDebugger triggering.
UPROPERTY(Config, EditAnywhere, Category="Debugging Tools")
TSet<FName> BreakToDebuggerExcludeCategories;
};

View File

@@ -12,7 +12,7 @@
#include "Blueprint/WidgetBlueprintLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/GameInstance.h"
#include "ProjectSettings.h"
#include "LuprexEditorSettings.h"
#include "Common.h"
#include "AnimQueue.h"
@@ -215,7 +215,7 @@ void ALuprexGameModeBase::InitializeGlobalState()
// Possibly tell the engine to connect to a server.
if (Playing) {
UlxServerSelection* Server = GetDefault<UlxProjectSettings>()->ActiveServer.LoadSynchronous();
UlxServerSelection* Server = GetDefault<UlxEditorSettings>()->ActiveServer.LoadSynchronous();
if (Server) {
FString Host = Server->Host.TrimStartAndEnd();
if (!Host.IsEmpty()) {
@@ -238,7 +238,7 @@ void ALuprexGameModeBase::InitializeGlobalState()
// If somebody generates a log message that's severe enough, break to debugger.
BreakToDebuggerLogVerbosityDevice.Reset(
new FlxBreakToDebuggerOutputDevice(BreakToDebuggerLogVerbosity));
new FlxBreakToDebuggerOutputDevice());
}
void ALuprexGameModeBase::EndPlay(const EEndPlayReason::Type EndPlayReason)

View File

@@ -68,10 +68,6 @@ public:
UFUNCTION(BlueprintCallable, Category = "Luprex|Miscellaneous", meta = (WorldContext = "WorldContextObject"))
static void TriggerReloadSource(const UObject *WorldContextObject);
// The sensitivity level at which a log message triggers a debugger breakpoint.
UPROPERTY(EditAnywhere, Category="Debugging Tools")
ElxBreakToDebuggerThreshold BreakToDebuggerLogVerbosity;
// Luprex socket system.
TUniquePtr<FlxSockets> Sockets;

View File

@@ -1 +0,0 @@
#include "ProjectSettings.h"

View File

@@ -242,3 +242,8 @@ void UlxUtilityLibrary::ValidateLuaExpr(
Status = w.ValidateLuaExpr(Code, ErrorMessage);
}
void UlxUtilityLibrary::LogLuprexConsoleMessage(const FString &Message)
{
UE_LOG(LogLuprexConsole, Warning, TEXT("%s"), *Message);
}

View File

@@ -144,4 +144,9 @@ public:
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Utility")
static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code);
// Log a message to the LogLuprexConsole category.
//
UFUNCTION(BlueprintCallable, Category = "Luprex|Utility")
static void LogLuprexConsoleMessage(const FString &Message);
};

4
TeppyNotes.txt Normal file
View File

@@ -0,0 +1,4 @@
- To run a server:
- cd luprex
- ./build/Linux/luprexstatic lpxserver

View File

@@ -18,6 +18,8 @@ public:
PrintChanneler print_channeler_;
eng::vector<Invocation> delayed_invocations_;
lua_State *lua_syntax_checker_;
eng::string auto_reconnect_target_;
double auto_reconnect_time_ = 0.0;
public:
LpxClient(EngineWrapper *w) : wrapper_(w) {
@@ -58,6 +60,10 @@ public:
// Clear any saved invocations
delayed_invocations_.clear();
// Clear auto-reconnect state.
auto_reconnect_target_.clear();
auto_reconnect_time_ = 0.0;
}
void set_initial_state_standalone() {
@@ -83,6 +89,10 @@ public:
// Clear any saved invocations
delayed_invocations_.clear();
// Clear auto-reconnect state.
auto_reconnect_target_.clear();
auto_reconnect_time_ = 0.0;
}
// When the world is in synchronous mode, there's no
@@ -108,10 +118,12 @@ public:
}
}
void abandon_server() {
void disconnect_and_reconnect() {
if (channel_)
{
set_initial_state_standalone();
auto_reconnect_target_ = channel_->target();
auto_reconnect_time_ = get_clock() + 5.0;
channel_.reset();
}
}
@@ -138,7 +150,7 @@ public:
// An ack is just a single byte, so there's nothing left to read.
if (unack_.empty()) {
// Invalid acknowledgement when theres' nothing in the unack queue.
abandon_server();
disconnect_and_reconnect();
return;
}
world_to_synchronous();
@@ -158,7 +170,7 @@ public:
}
// dbc.dump(...);
} catch (const StreamException &sexcept) {
abandon_server();
disconnect_and_reconnect();
return;
}
}
@@ -179,11 +191,11 @@ public:
} else if (message_type == util::MSG_DIFF) {
receive_diff_from_server(&body);
} else {
abandon_server();
disconnect_and_reconnect();
return false;
}
if (!body.empty()) {
abandon_server();
disconnect_and_reconnect();
return false;
}
return true;
@@ -248,7 +260,7 @@ public:
if (channel_ != nullptr) {
if (channel_->closed()) {
util::dprint("server closed connection: ", channel_->error());
abandon_server();
disconnect_and_reconnect();
} else {
while (true) {
if (!receive_message_from_server(channel_->in())) break;
@@ -256,6 +268,10 @@ public:
}
world_to_asynchronous();
}
} else {
if ((!auto_reconnect_target_.empty()) && (get_clock() > auto_reconnect_time_)) {
set_initial_state_connect(auto_reconnect_target_);
}
}
set_have_prints(print_channeler_.have_prints(world_->get_printbuffer(actor_id_)));

View File

@@ -77,6 +77,7 @@ lua_State *LuaCoreStack::newstate (lua_Alloc allocf) {
LS.rawset(LuaRegistry, "persist", LuaNewTable);
LS.rawset(LuaRegistry, "unpersist", LuaNewTable);
LS.rawset(LuaRegistry, "funcnames", LuaNewTable);
LS.rawset(LuaRegistry, "sourcedb", LuaNewTable);
// Tag the registry and global environment with their tabletypes.
LS.settabletype(LuaRegistry, LUA_TT_REGISTRY);

View File

@@ -171,6 +171,7 @@ public:
LuaExtraArgs(int i, int s) : index_(i), size_(s) {}
LuaSpecial operator[] (int n) const { return LuaSpecial(index_ + n); }
int index(int n) const { return index_ + n; }
int size() const { return size_; }
friend class LuaCoreStack;

View File

@@ -132,16 +132,6 @@ static void get_info_table(LuaCoreStack &LS, LuaSlot db, LuaSlot info, const eng
LS.rawset(info, "name", fn);
}
static void calculate_loadresult(LuaCoreStack &LS0, LuaSlot info, const eng::string &fn, const eng::string &code) {
LuaVar loadresult;
LuaExtStack LS(LS0.state(), loadresult);
if (code == "") {
LS.rawset(info, "loadresult", "missing or empty source file");
} else {
LS.load(loadresult, code, fn);
LS.rawset(info, "loadresult", loadresult);
}
}
void SourceDB::diff(const SourceDB &auth, StreamBuffer *sb) {
LuaVar sdb, sfn, sinfo, shash, sseq;
@@ -202,8 +192,8 @@ void SourceDB::diff(const SourceDB &auth, StreamBuffer *sb) {
bool SourceDB::patch(StreamBuffer *sb, DebugCollector *dbc) {
lua_State *L = lua_state_;
LuaVar db, info;
LuaExtStack LS(L, db, info);
LuaVar db, info, closure;
LuaExtStack LS(L, db, info, closure);
LS.rawget(db, LuaRegistry, "sourcedb");
int nupdates = sb->read_int32();
for (int i = 0; i < nupdates; i++) {
@@ -219,24 +209,27 @@ bool SourceDB::patch(StreamBuffer *sb, DebugCollector *dbc) {
if (code != "\001") {
LS.rawset(info, "code", code);
LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code)));
calculate_loadresult(LS, info, fn, code);
LS.load(closure, code, fn);
LS.rawset(info, "loadresult", closure);
}
}
}
if (nupdates > 0) rebuild();
return (nupdates > 0);
}
void SourceDB::set(const eng::string &fn, const eng::string &code, int sequence) {
lua_State *L = lua_state_;
LuaVar db, info;
LuaExtStack LS(L, db, info);
LuaVar db, info, closure;
LuaExtStack LS(L, db, info, closure);
LS.rawget(db, LuaRegistry, "sourcedb");
get_info_table(LS, db, info, fn);
LS.rawset(info, "sequence", sequence);
LS.rawset(info, "code", code);
LS.rawset(info, "fingerprint", "");
LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code)));
calculate_loadresult(LS, info, fn, code);
LS.load(closure, code, fn);
LS.rawset(info, "loadresult", closure);
}
eng::string SourceDB::get(const eng::string &fn) {
@@ -262,19 +255,15 @@ eng::string SourceDB::get(const eng::string &fn) {
return util::ss(seqno, ":", ccode, ":", cloadresult);
}
void SourceDB::update(const util::LuaSourceVec &source) {
eng::string SourceDB::update(const util::LuaSourceVec &source) {
lua_State *L = lua_state_;
LuaVar sourcedb, info;
LuaExtStack LS(L, sourcedb, info);
LuaVar newsourcedb, oldsourcedb, info, closure;
LuaExtStack LS(L, newsourcedb, oldsourcedb, info, closure);
// Get and clear the source database.
LS.rawget(sourcedb, LuaRegistry, "sourcedb");
if (!LS.istable(sourcedb)) {
LS.newtable(sourcedb);
LS.rawset(LuaRegistry, "sourcedb", sourcedb);
}
LS.cleartable(sourcedb, true);
eng::ostringstream errors;
// Step one. Build the new sourcedb.
LS.newtable(newsourcedb);
for (int i = 0; i < int(source.size()); i++) {
const eng::string &file = source[i].first;
const eng::string &code = source[i].second;
@@ -283,9 +272,32 @@ void SourceDB::update(const util::LuaSourceVec &source) {
LS.rawset(info, "code", code);
LS.rawset(info, "hash", util::hash_to_hex(util::hash_string(code)));
LS.rawset(info, "sequence", i + 1);
calculate_loadresult(LS, info, file, code);
LS.rawset(sourcedb, file, info);
LS.load(closure, code, file);
LS.rawset(info, "loadresult", closure);
if (LS.isstring(closure)) {
errors << file << ":" << LS.ckstring(closure) << std::endl;
}
LS.rawset(newsourcedb, file, info);
}
// If there were compile errors, return the errors and don't update the env.
if (!errors.view().empty()) {
return errors.str();
}
// Cache the old source database and store the new one.
LS.rawget(oldsourcedb, LuaRegistry, "sourcedb");
LS.rawset(LuaRegistry, "sourcedb", newsourcedb);
// Rebuild the source database.
eng::string rebuild_errors = rebuild();
// Restore the old state, if there were errors.
if (!rebuild_errors.empty()) {
LS.rawset(LuaRegistry, "sourcedb", oldsourcedb);
rebuild();
}
return rebuild_errors;
}
// Delete everything from the global environment.
@@ -411,14 +423,11 @@ eng::string SourceDB::get_source(const eng::string &fn)
return LS.ckstring(code);
}
eng::vector<eng::string> SourceDB::modules() {
eng::vector<eng::string> result;
LuaVar sourcedb, key, info, seq;
LuaExtStack LS(lua_state_, sourcedb, key, info, seq);
result.push_back("CORE");
// Get the source database.
LS.rawget(sourcedb, LuaRegistry, "sourcedb");
if (!LS.istable(sourcedb)) {
@@ -439,35 +448,38 @@ eng::vector<eng::string> SourceDB::modules() {
return result;
}
void SourceDB::rebuild_core() {
eng::string SourceDB::rebuild_module(const eng::string &mod) {
LuaVar sourcedb, info, closure;
LuaExtStack LS(lua_state_, sourcedb, info, closure);
LS.rawget(sourcedb, LuaRegistry, "sourcedb");
LS.rawget(info, sourcedb, mod);
if (!LS.istable(info)) {
return util::ss("No such module: ", mod);
}
LS.rawget(closure, info, "loadresult");
if (!LS.isfunction(closure)) {
return util::ss(mod, ":", LS.ckstring(closure));
}
lua_pushvalue(lua_state_, closure.index());
return traceback_pcall(lua_state_, 0, 0);
}
eng::string SourceDB::rebuild() {
source_clear_globals(lua_state_);
source_load_cfunctions(lua_state_);
source_load_cconstants(lua_state_);
eng::ostringstream errors;
for (const eng::string &mod: modules()) {
eng::string err = rebuild_module(mod);
if (!err.empty()) errors << err << std::endl;
}
rebuild_funcnames();
return errors.str();
}
eng::string SourceDB::rebuild_module(const eng::string &mod) {
if (mod == "CORE") {
rebuild_core();
return "";
} else {
LuaVar sourcedb, info, closure;
LuaExtStack LS(lua_state_, sourcedb, info, closure);
LS.rawget(sourcedb, LuaRegistry, "sourcedb");
if (!LS.istable(sourcedb)) {
return "SourceDB not initialized";
}
LS.rawget(info, sourcedb, mod);
if (!LS.istable(info)) {
return util::ss("No such module: ", mod);
}
LS.rawget(closure, info, "loadresult");
if (!LS.isfunction(closure)) {
return util::ss(mod, ":", LS.ckstring(closure));
}
lua_pushvalue(lua_state_, closure.index());
return traceback_pcall(lua_state_, 0, 0);
}
}
void SourceDB::init(lua_State *L) {
register_lua_builtins();
@@ -482,8 +494,7 @@ void SourceDB::init(lua_State *L) {
LS.set(nullstring, "");
LS.setmetatable(nullstring, classtab);
// Rebuild the global environment.
rebuild_core();
rebuild();
// We need to register all C functions with the eris permanents tables.
LS.rawget(persist, LuaRegistry, "persist");
@@ -516,15 +527,22 @@ void SourceDB::serialize_source(const util::LuaSourceVec &sv, StreamBuffer *sb)
}
}
void SourceDB::deserialize_source(util::LuaSourceVec *sv, StreamBuffer *sb) {
sv->clear();
int count = sb->read_int32();
if ((count < 0) || (count > 10000)) throw StreamCorruption();
for (int i = 0; i < count; i++) {
eng::string fn = sb->read_string();
eng::string code = sb->read_string();
sv->emplace_back(fn, code);
util::LuaSourceVec SourceDB::deserialize_source(std::string_view datapack) {
StreamBuffer sb(datapack);
util::LuaSourceVec sv;
try {
sv.clear();
int count = sb.read_int32();
if ((count < 0) || (count > 10000)) throw StreamCorruption();
for (int i = 0; i < count; i++) {
eng::string fn = sb.read_string();
eng::string code = sb.read_string();
sv.emplace_back(fn, code);
}
} catch (const StreamException &ex) {
sv.clear();
}
return sv;
}
// Register lua builtins.
@@ -630,7 +648,6 @@ bool SourceDB::search_docs(const eng::string &substring, std::ostream &ostream)
// Search the lua source code.
for (const eng::string &module : modules()) {
if (module == "CORE") continue;
eng::string code = get_source(module);
if (code.empty()) continue;
util::StringVec lines = util::split_lines(code);

View File

@@ -132,58 +132,31 @@ private:
public:
void init(lua_State *L);
// Update
//
// Update the database using the specified lua source code.
// Compiles these files using lua's "load" function.
//
void update(const util::LuaSourceVec &source);
// modules
//
// Returns a list of all the modules. The first item in the list
// is always the string "CORE" which represents the lua core
// functionality with all the builtins. This is then followed by
// all the lua sourcefiles in the correct order.
// modules()
//
// Returns a list of all the lua modules, in the proper order.
//
eng::vector<eng::string> modules();
// get_source
// Update
//
// Get the source code for a given module.
// Try to compile and load the specified source. Then, rebuild the
// global environment.
//
eng::string get_source(const eng::string &fn);
// rebuild_module
// If this generates any errors, puts back the old code, and rebuilds
// the global environment using the old code.
//
// Returns any error messages. If this returns empty string, it means
// there were no errors and the code was successfully update. If there
// are any error messages, it means we restored the old code as best as
// possible.
//
// To rebuild the lua environment, fetch the module list, then
// call rebuild_module on each module in turn. This will return
// an error message for the module, or empty string if no error.
//
// This is a thin wrapper around traceback_pcall. The return
// value is the return value of traceback_pcall.
//
eng::string rebuild_module(const eng::string &mod);
// rebuild_core
//
// This is equivalent to rebuild_module("CORE"). Clears the environment
// and installs all the builtins. No error conditions.
//
void rebuild_core();
// rebuild_funcnames
//
// Traverses the global environment and populates the registry "funcnames"
// table, mapping each closure to its name.
//
void rebuild_funcnames();
eng::string update(const util::LuaSourceVec &source);
// Difference transmission.
//
// Note: The patch routine applies the differences to the source
// database, and if there are any changes, it does a source rebuild.
// The patch routine returns true if anything was modified.
//
//
void diff(const SourceDB &auth, StreamBuffer *sb);
bool patch(StreamBuffer *sb, DebugCollector *dbc);
@@ -213,7 +186,54 @@ public:
// Serialize and unserialize a source vector.
//
static void serialize_source(const util::LuaSourceVec &sv, StreamBuffer *sb);
static void deserialize_source(util::LuaSourceVec *sv, StreamBuffer *sb);
static util::LuaSourceVec deserialize_source(std::string_view datapack);
private:
//////////////////////////////////////////////////////////////////////////
//
// Internal implementation stuff.
//
//////////////////////////////////////////////////////////////////////////
// rebuild
//
// Rebuild the global environment from the sourcedb:
//
// * Clears the environment
// * Installs the builtins
// * Executes all the closures in the sourcedb.
// * Regenerates the function-names table.
//
// The closures may generate errors, if so, this returns the error
// messages. Note that if there are errors, there is no automatic cleanup.
//
eng::string rebuild();
// get_source
//
// Get the source code for a given module.
//
eng::string get_source(const eng::string &fn);
// rebuild_module
//
// To rebuild the lua environment, fetch the module list, then
// call rebuild_module on each module in turn. This will return
// an error message for the module, or empty string if no error.
//
// This is a thin wrapper around traceback_pcall. The return
// value is the return value of traceback_pcall.
//
eng::string rebuild_module(const eng::string &mod);
// rebuild_funcnames
//
// Traverses the global environment and populates the registry "funcnames"
// table, mapping each closure to its name.
//
void rebuild_funcnames();
};

View File

@@ -55,7 +55,8 @@ public:
virtual void event_access(AccessKind kind, int64_t place_id, std::string_view datapk, StreamBuffer *retpk) override {
switch (kind) {
case AccessKind::INVOKE_LUA_SOURCE: {
world_->update_source(datapk, 0);
Invocation inv(kind, place_id, place_id, datapk);
world_->invoke(0, inv);
run_unittests(world_->state());
stop_driver();
break;

View File

@@ -607,20 +607,13 @@ LuaDefine(tangible_start, "tangible,function,arg1,arg2...",
"|to continue running until it blocks. But from that point"
"|forward, the thread will never be resumed for any reason.") {
int top = lua_gettop(L);
if (top < 2) {
luaL_error(L, "Not enough arguments to tangible.start");
return 0;
}
int varlen = top - 2;
World *w = World::fetch_global_pointer(L);
w->guard_blockable(L, "tangible.start");
LuaArg place, fname;
LuaExtraArgs extra;
LuaVar mt, classtab, plthreads, thread, thinfo, func, tanlist;
LuaDefStack LS(L, mt, classtab, plthreads, thread, thinfo, func, tanlist);
LuaSpecial place(1);
LuaSpecial fname(2);
LuaDefStack LS(L, place, fname, extra, mt, classtab, plthreads, thread, thinfo, func, tanlist);
// If they passed in a single tangible, convert it to a tangible list.
int64_t place_id = LS.tanid(place);
@@ -677,10 +670,10 @@ LuaDefine(tangible_start, "tangible,function,arg1,arg2...",
// Create a new thread, set up function and arguments.
lua_State *CO = LS.newthread(thread);
lua_pushvalue(L, func.index());
for (int i = 0; i < varlen; i++) {
lua_pushvalue(L, i + 3);
for (int i = 0; i < extra.size(); i++) {
lua_pushvalue(L, extra.index(i));
}
lua_xmove(L, CO, varlen + 1);
lua_xmove(L, CO, extra.size() + 1);
// Create the thread info table.
LS.newtable(thinfo);

View File

@@ -310,10 +310,13 @@ int64_t World::connection_create() {
assert(stack_is_clear());
int64_t id = id_global_pool_.get_one();
{
LuaVar database, classtab, mt, func;
LuaExtStack LS(state(), database, classtab, mt, func);
LuaVar database, classtab, config, mt, func;
LuaExtStack LS(state(), database, classtab, config, mt, func);
Tangible *tan = tangible_make(LS, database, id);
// A blank config table to pass to 'init'
LS.newtable(config);
// Set the login flags.
if (is_authoritative()) {
tan->client_id_ = id;
@@ -327,7 +330,9 @@ int64_t World::connection_create() {
if (is_authoritative()) {
LS.rawget(func, classtab, "init");
spawn(LS, id, id, func, 0, false);
lua_pushvalue(state(), database.index());
lua_pushvalue(state(), config.index());
spawn(LS, id, id, func, 2, false);
}
}
connections_.emplace(id, id);
@@ -572,71 +577,6 @@ void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view
clear_lthread_state();
}
// This is called from World::update_source, and also
// from World::patch_source in the difference transmitter.
//
// When called from the difference transmitter, we suppress
// error messages.
//
// For the moment, errors are channeled to util::dprint,
// and 'print' statements just go to std::cerr. Neither
// of these is ideal. We need to get serious about setting
// up error handling.
//
// We also need to figure out a solution for what happens if
// some lua source file tries to modify, say, tangible state
// in top-level code.
//
bool World::rebuild_sourcedb(int64_t actor_id) {
int successes = 0;
int failures = 0;
for (const eng::string &mod: source_db_.modules()) {
open_lthread_state(0, 0, 0, false);
eng::string err = source_db_.rebuild_module(mod);
eng::string prints = lthread_prints_.str();
clear_lthread_state();
if (err.empty()) successes ++;
else failures ++;
if ((!err.empty()) || (!prints.empty())) {
lthread_prints_ << "Compiling " << mod << ":" << std::endl;
if (!err.empty()) lthread_prints_ << err << std::endl;
if (!prints.empty()) lthread_prints_ << prints;
util::dprintview(lthread_prints_.view());
if (actor_id != 0) lthread_prints_to_actor(actor_id);
}
}
source_db_.rebuild_funcnames();
lthread_prints_ << "Compiled " << successes << " modules successfully." << std::endl;
if (failures > 0) {
lthread_prints_ << "Compiled " << failures << " modules with errors." << std::endl;
}
util::dprintview(lthread_prints_.view());
if (actor_id > 0) lthread_prints_to_actor(actor_id);
return (failures == 0);
}
bool World::update_source(const util::LuaSourceVec &source, int64_t actor_id) {
assert(stack_is_clear());
source_db_.update(source);
return rebuild_sourcedb(actor_id);
assert(stack_is_clear());
}
bool World::update_source(std::string_view sourcepack, int64_t actor_id) {
if (sourcepack.empty()) {
return false;
}
try {
StreamBuffer sb(sourcepack);
util::LuaSourceVec sv;
SourceDB::deserialize_source(&sv, &sb);
return update_source(sv, actor_id);
} catch (const StreamException &ex) {
return false;
}
}
void World::http_response(const HttpParser &response) {
// Find the request.
auto iter = http_requests_.find(response.request_id());
@@ -1019,23 +959,41 @@ void World::invoke_lua_source(int64_t actor_id, int64_t place_id, std::string_vi
if (actor_id != place_id) return;
// Check if this is the first time we're loading the source.
bool brand_new = (source_db_.modules().size() == 1);
bool brand_new = (source_db_.modules().size() == 0);
// Compile and load the source.
bool success = update_source(datapack, actor_id);
// Deserialize the datapack.
util::LuaSourceVec sv = SourceDB::deserialize_source(datapack);
if (sv.empty()) return;
// Try to compile the code.
open_lthread_state(actor_id, actor_id, 0, false);
eng::string errors = source_db_.update(sv);
if (errors.empty()) {
lthread_prints_ << "Compiled source successfully.\n";
} else {
lthread_prints_ << "Compiling source: \n" << errors;
}
util::dprint(lthread_prints_.view());
lthread_prints_to_actor(actor_id);
// Call world.init
if (brand_new) {
if (success) {
if (errors.empty()) {
{
lua_State *L = state();
LuaVar lclass, lfunc;
LuaExtStack LS(L, lclass, lfunc);
LuaVar lclass, lfunc, tangibles, actor, config;
LuaExtStack LS(L, lclass, lfunc, tangibles, actor, config);
LS.rawget(tangibles, LuaRegistry, "tangibles");
LS.rawget(actor, tangibles, actor_id);
LS.newtable(config);
LS.getclass(lclass, "world");
if (LS.classname(lclass) != "") {
LS.rawget(lfunc, lclass, "init");
spawn(LS, actor_id, place_id, lfunc, 0, false);
lua_pushvalue(L, actor.index());
lua_pushvalue(L, config.index());
spawn(LS, actor_id, place_id, lfunc, 2, false);
}
}
run_scheduled_threads();

View File

@@ -290,11 +290,7 @@ void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) {
void World::patch_source(StreamBuffer *sb, DebugCollector *dbc) {
DebugBlock dbb(dbc, "patch_source");
bool modified = source_db_.patch(sb, dbc);
if (modified) {
rebuild_sourcedb(0);
DebugLine(dbc) << "Source DB rebuilt";
}
source_db_.patch(sb, dbc);
}
void World::diff_source(World *master, StreamBuffer *sb) {
@@ -359,6 +355,7 @@ void World::diff_globals(World *master, StreamBuffer *sb) {
int64_t World::patch(StreamBuffer *sb, DebugCollector *dbc) {
DebugBlock dbb(dbc, "patch");
clear_lthread_state();
int64_t actor_id = patch_actor(sb, dbc);
patch_visible(sb, dbc);
bool full = sb->read_bool();

View File

@@ -242,23 +242,6 @@ public:
//
SourceDB &get_source() { return source_db_; }
// Rebuild the global environment from the source database.
//
// Error messages go to the specified actor, and also dprint.
//
// Returns true if the rebuild goes without errors.
//
bool rebuild_sourcedb(int64_t actor_id);
// Update the source database from disk, then rebuild the global environment.
//
// Error messages go to the specified actor, and also dprint.
//
// Returns true if the update goes without errors.
//
bool update_source(const util::LuaSourceVec &source, int64_t actor_id);
bool update_source(std::string_view sourcepk, int64_t actor_id);
// Supply an HTTP response to an outstanding HTTP request.
//
void http_response(const HttpParser &response);

View File

@@ -4,24 +4,94 @@ makeclass('probe')
makeclass('invoke')
makeclass('cube')
makeclass('sphere')
makeclass('pylon')
makeclass('roster')
function pretty(t, indent, seen)
indent = indent or 0
seen = seen or {}
if type(t) ~= "table" then return tostring(t) end
if seen[t] then return "<cycle>" end
seen[t] = true
local spacing = string.rep(" ", indent)
local out = "{\n"
for k, v in pairs(t) do
out = out .. spacing .. " [" .. tostring(k) .. "] = "
out = out .. pretty(v, indent + 1, seen) .. ",\n"
end
return out .. spacing .. "}"
end
-- tangible.redirect(actor1,actor2)
-- tangible.forcedisconnect(actor)
-- tangible.keepactor(actor) -- Do not autodelete upon disconnect
-- tangible.delete(actor) -- If actor is a connected player it will disconnect
-- This gets called on every login except the admin user.
function login.init()
function login.init(self, config)
local player = global.get("nextplayer")
global.set("nextplayer", player + 1)
dprint("login.init initializing player ", player)
actor.player = player
tangible.keepactor(actor) -- do not delete this login when the client disconnects
tangible.animinit{tan=actor, anim={bp="character", mesh="manny", plane="earth", xyz={player * 100, 0, 90}}}
printf("login.init initializing %p, player %d", self, player)
self.player = player
self.color={0,0,0,0,0,0,0,0,0,0,0,0}
self.kills=0
self.killed=0
tangible.animinit{tan=self, anim={bp="character", mesh="manny", plane="earth", xyz={player * 100, 0, 90}}}
tangible.start(self, login.startscanning)
end
function login.startscanning()
printf("Scanning started. Actor=%p Place=%p", actor, place)
while true do
login.onescan()
wait(1)
end
end
function login.onescan()
local nearby=tangible.find{plane=tangible.animfinal(actor).plane,center=tangible.animfinal(actor).xyz,radius=1000,shape="cylinder"}
for k,v in pairs(nearby) do
if classname(v)=="pylon" then actor.color[v.color]=time()+30 end
if classname(v)=="player" then
if actor.team==v.team then
for ck,cv in v.color do actor.color[ck]=math.max(actor.color[ck],cv) end
else
local score1=0
local score2=0
for ck,cv in actor.color do if time()-cv<0 then score1=score1+1 end end
for ck,cv in v.color do if time()-cv<0 then score2=score2+1 end end
if score1-score2>0 then
-- kill player 2
end
end
end
end
end
-- This gets called on the admin user. You can call login.init in here if you want.
function world.init()
dprint("world.init")
function world.init(self, config)
print("world.init")
global.set("nextplayer", 0)
tangible.build{class=roster, anim={plane="earth", xyz={2000,0,0} } }
tangible.build{class=cube, anim={plane="earth", xyz={500,-100,0}, mat_color={1,0,0}}}
tangible.build{class=sphere, anim={plane="earth", xyz={500,100,0}, mat_color={0,0,1}}}
login.init()
login.init(self, config)
end
function roster.init(self,config)
self.character={}
end
function world.buildpylon()
print("Building pylon")
local radius=1000
tangible.build{class=pylon,plane="earth",xyz={math.random(-radius,radius),math.random(-radius,radius),0}}.color=math.random(1,12)
end
function invoke.move(action, xyz, facing)
@@ -38,20 +108,60 @@ function login.lookmenu(add)
add("Redirect", function() tangible.redirect(actor, place) end)
end
function cube.lookmenu(add)
add("Cube A", function () dprint("Doing Cube A") end)
add("Cube B", function () dprint("Doing Cube B") end)
add("Cube C", function () dprint("Doing Cube C") end)
add("Cube Hi", function () dprint("Doing Cube Hi") end)
add("Cube Bye", function () dprint("Doing Cube Bye") end)
add("Cube Yo", function () dprint("Doing Cube Yo") end)
add("Cube Z", function () dprint("Doing Cube Z") end)
-- Four cases: Unnamed to new, Unnamed to existing, Named to new, Named to Existing
function playas(who)
printf("Playas %lP",place)
if not actor.name and not place.character[who] then -- Unnamed to new
printf("Case 1: Naming this character "..who)
actor.name=who
tangible.keepactor(actor)
place.character[who]=actor
elseif actor.name and not place.character[who] then -- Named to new (Hard one)
printf("Case 2: Creating new character "..who)
printf("Case 2.0")
local nc=tangible.build{class=login, anim={plane="earth", xyz={0,0,0} } }
printf("Case 2.1")
nc.name=who
printf("Case 2.2")
tangible.keepactor(nc)
printf("Case 2.3")
place.character[who]=nc
printf("Case 2.4: ...Place is now "..place)
tangible.redirect(actor,nc)
elseif not actor.name and place.character[who] then
printf("Case 3: Logging in from unnamed to existing character "..who)
tangible.redirect(actor,place.character[who])
elseif actor.name and place.character[who] then
printf("Case 4: Logging in from "..actor.name.." to existing character "..who)
tangible.redirect(actor,place.character[who])
end
end
function roster.lookmenu(add)
for _,name in ipairs { "Albert", "Betty", "Cornelius" } do
add("Play as "..name..(place.character[name] and "" or "*"), function() playas(name) end)
end
end
function cube.lookmenu(add)
add("Cube A", function () print("Doing Cube A") end)
add("Cube B", function () print("Doing Cube B") end)
add("Cube C", function () print("Doing Cube C") end)
add("Cube Hi", function () print("Doing Cube Hi") end)
add("Cube Bye", function () print("Doing Cube Bye") end)
add("Cube Yo", function () print("Doing Cube Yo") end)
add("Cube Z", function () print("Doing Cube Z") end)
end
function sphere.lookhotkeys(add)
add("FaceL", "Sphere Hi", function () dprint("Doing Sphere Hi") end)
add("FaceM", "Sphere Bye", function () dprint("Doing Sphere Bye") end)
add("FaceR", "Sphere Yo", function () dprint("Doing Sphere Yo") end)
add("FaceL", "Sphere Hi", function () print("Doing Sphere Hi") end)
add("FaceM", "Sphere Bye", function () print("Doing Sphere Bye") end)
add("FaceR", "Sphere Yo", function () print("Doing Sphere Yo") end)
end
function sphere.tick(foo)
print("Tick")
end

View File