Lots of work on the lua read-eval-print loop

This commit is contained in:
2026-05-21 18:41:09 -04:00
parent f7983b1f02
commit 2bfa3024f1
15 changed files with 70 additions and 120 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,20 +1,10 @@
* UE Wingman rename functions.
* ue Wingman 'structprop' doesn't work for UWingXXXRef types, or for Widget slots. It needs to be implemented on top of getdetails.
* In the console, do not allow multi-line lua expressions unless it's something that reasonably should be multi-line, like a function definition or an if-statement.
* Keyboard Event Handling
* Menus
* Skeletal Mesh Tangible
* Implement Interactive Temporary Variables
* A better text console
* Get rid of 3x3 Gridpanel stuff
* Object-Oriented Lua Support

View File

@@ -10,6 +10,8 @@
#include "Kismet/GameplayStatics.h"
#include "Blueprint/UserWidget.h"
#include "Components/GridPanel.h"
#include "Components/CanvasPanelSlot.h"
#include "Components/Widget.h"
#include "InputMappingContext.h"
#include "EnhancedInputComponent.h"
#include "Animation/AnimSequenceBase.h"
@@ -156,65 +158,28 @@ bool UlxUtilityLibrary::LineTraceThroughPixel(const APlayerController* PlayerCon
return false;
}
void UlxUtilityLibrary::SetPositionOfGridPanelMiddleCell(UGridPanel *GridPanel, FVector2D UpperLeftXY, FVector2D LowerRightXY)
void UlxUtilityLibrary::ConfigureCanvasPanelSlot(UObject *Target, FAnchors Anchors, FVector2D Position, FVector2D Size, FVector2D Alignment, bool SizeToContent)
{
if ((GridPanel == nullptr) || (GridPanel->ColumnFill.Num() != 3) || (GridPanel->RowFill.Num() != 3))
UCanvasPanelSlot *CanvasSlot = Cast<UCanvasPanelSlot>(Target);
if (CanvasSlot == nullptr)
{
UE_LOG(LogBlueprint, Error, TEXT("SetPositionOfGridPanelMiddleCell only works on 3x3 GridPanels."));
UWidget *Widget = Cast<UWidget>(Target);
if (Widget != nullptr)
{
CanvasSlot = Cast<UCanvasPanelSlot>(Widget->Slot);
}
}
if (CanvasSlot == nullptr)
{
UE_LOG(LogBlueprint, Error, TEXT("ConfigureCanvasPanelSlot: object is not a CanvasPanelSlot, and is not a Widget in a CanvasPanel."));
return;
}
if ((LowerRightXY.X < UpperLeftXY.X) || (LowerRightXY.Y < UpperLeftXY.Y))
{
UE_LOG(LogBlueprint, Error, TEXT("LowerRightXY must be greater than or equal to UpperLeftXY"));
return;
}
UpperLeftXY.X = FMath::Clamp(UpperLeftXY.X, 0.0f, 1.0f);
UpperLeftXY.Y = FMath::Clamp(UpperLeftXY.Y, 0.0f, 1.0f);
LowerRightXY.X = FMath::Clamp(LowerRightXY.X, 0.0f, 1.0f);
LowerRightXY.Y = FMath::Clamp(LowerRightXY.Y, 0.0f, 1.0f);
GridPanel->SetRowFill(0, UpperLeftXY.Y);
GridPanel->SetRowFill(1, LowerRightXY.Y - UpperLeftXY.Y);
GridPanel->SetRowFill(2, 1.0 - LowerRightXY.Y);
GridPanel->SetColumnFill(0, UpperLeftXY.X);
GridPanel->SetColumnFill(1, LowerRightXY.X - UpperLeftXY.X);
GridPanel->SetColumnFill(2, 1.0 - LowerRightXY.X);
}
void UlxUtilityLibrary::GetPositionOfGridPanelMiddleCell(UGridPanel *GridPanel, FVector2D &UpperLeftXY, FVector2D &LowerRightXY)
{
TArray<float> &Col = GridPanel->ColumnFill;
TArray<float> &Row = GridPanel->RowFill;
// Set default return value for error situations.
UpperLeftXY.X = 0.0;
LowerRightXY.X = 1.0;
UpperLeftXY.Y = 0.0;
LowerRightXY.Y = 1.0;
if ((GridPanel == nullptr) || (Row.Num() != 3) || (Col.Num() != 3))
{
UE_LOG(LogBlueprint, Error, TEXT("SetPositionOfGridPanelMiddleCell only works on 3x3 GridPanels."));
return;
}
double TotalX = Col[0] + Col[1] + Col[2];
double TotalY = Row[0] + Row[1] + Row[2];
if (TotalX > 0)
{
UpperLeftXY.X = Col[0] / TotalX;
LowerRightXY.X = (Col[0] + Col[1]) / TotalX;
}
if (TotalY > 0)
{
UpperLeftXY.Y = Row[0] / TotalY;
LowerRightXY.Y = (Row[0] + Row[1]) / TotalY;
}
CanvasSlot->SetAnchors(Anchors);
CanvasSlot->SetAlignment(Alignment);
CanvasSlot->SetPosition(Position);
CanvasSlot->SetSize(Size);
CanvasSlot->SetAutoSize(SizeToContent);
}
ElxUsedOrNotUsed UlxUtilityLibrary::IsKeyUsedByMappingContext(const FKey &Key, const UInputMappingContext *MappingContext)

View File

@@ -7,6 +7,7 @@
#include "Input/Events.h"
#include "Common.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Components/CanvasPanelSlot.h"
#include "UtilityLibrary.generated.h"
@@ -91,51 +92,15 @@ public:
ETraceTypeQuery TraceChannel, bool bTraceComplex, EDrawDebugTrace::Type DrawDebugType, bool bIgnorePlayerPawn,
const TArray<AActor*>& ActorsToIgnore, FHitResult& HitResult);
// Set Position of GridPanel Middle Cell
//
// Sometimes, you want to specify the position of a widget, and you
// don't want to specify the position in slate units, instead, you
// want to specify the position using fractions: ie, (0,0) is the
// upper left corner of the screen, and (1,1) is the lower-right corner.
//
// One way to accomplish this is to put your widget in the middle cell
// of a 3x3 GridPanel. Then, you can position it by adjusting the grid
// fill rules. This utility routine can do the math necessary to
// correctly populate those fill rules.
//
// This routine must be passed a 3x3 GridPanel. This will reposition
// the middle cell. You must specify the upper-left and lower-right
// corners of the middle cell as fractions between (0,0) and (1,1).
//
// Be aware that if the content of a grid cell overflows the amount of
// space allocated for it, then the grid will adjust to make room.
// But that will mean that the grid is no longer faithful to the
// positions specified in its fill rules. One way to ensure that the
// grid remains faithful to its fill rules is to put an Overlay
// into the GridPanel cell, then put -1000 padding into the
// Overlay's GridPanel slot, then put +1000 padding into the Overlay
// slot. The two paddings cancel each other out, leaving the item in
// the Overlay at the originally-intended position. But if the item
// in the overlay overflows, it doesn't cause the Grid to deform.
// Instead, the item exceeds the bounds of the grid cell, but it leaves
// the grid cell where it belongs.
//
UFUNCTION(BlueprintCallable, Category="Widget")
static void SetPositionOfGridPanelMiddleCell(UGridPanel *GridPanel, FVector2D UpperLeftXY, FVector2D LowerRightXY);
// Get Position of GridPanel Middle Cell
// Configure a CanvasPanelSlot's parameters in a single call.
//
// The routine must be passed a 3x3 GridPanel. This will return the
// position of the middle cell of the gridpanel, expressed on a scale
// from (0,0) to (1,1).
// Target must be either a UCanvasPanelSlot directly, or a UWidget whose
// Slot is a UCanvasPanelSlot. If it is neither, logs an error and
// does nothing.
//
// The numbers returned by this routine are based entirely on the
// GridPanel fill rules. If an item in the grid is overflowing its
// allocated space, causing the grid to deform, then that won't be
// reflected in the output of this routine.
//
UFUNCTION(BlueprintPure, Category="Widget")
static void GetPositionOfGridPanelMiddleCell(UGridPanel *GridPanel, FVector2D &UpperLeftXY, FVector2D &LowerRightXY);
UFUNCTION(BlueprintCallable, Category = "Widget", meta = (SizeToContent = "true"))
static void ConfigureCanvasPanelSlot(UObject *Target, FAnchors Anchors, FVector2D Position, FVector2D Size, FVector2D Alignment, bool SizeToContent);
// Check if a given key is used by the specified mapping context.
//

View File

@@ -336,7 +336,7 @@ eng::string LuaCoreStack::load(LuaSlot result, std::string_view code, std::strin
const char *str = lua_tolstring(L_, -1, &len);
eng::string message(str, len);
lua_pop(L_, 1);
if (sv::has_suffix(message, "near <eof>"))
if (sv::has_suffix(message, "near <eof>") && sv::is_possible_long_lua_expression(code))
{
message = "truncated lua";
}

View File

@@ -192,7 +192,6 @@ bool PrintChanneler::channel(const PrintBuffer *printbuffer, StreamBuffer *sb) {
line_ = printbuffer->first_line();
}
while (line_ < printbuffer->first_unchecked()) {
sb->write_bytes("|");
sb->write_bytes(printbuffer->nth(line_));
sb->write_bytes("\n");
line_ += 1;

View File

@@ -200,6 +200,24 @@ bool is_lua_comment(string_view s) {
return s.substr(start, 2) == "--";
}
bool is_possible_long_lua_expression(string_view s) {
read_space(s);
string_view id = read_lua_identifier(s);
if (id.empty()) return false;
if ((id == "function") || (id == "if") || (id == "while") || (id == "for") || (id == "repeat") || (id == "do")) return true;
if (id == "local")
{
read_space(s);
id = read_lua_identifier(s);
}
read_space(s);
read_prefix(s, "="); // If not present, returns false but we continue anyway.
read_space(s);
if (has_prefix(s, "[")) return true;
if (has_prefix(s, "(")) return true;
return false;
}
bool is_whitespace(string_view s) {
for (int i = 0; i < int(s.size()); i++) {
if (!ascii_isspace(s[i])) {

View File

@@ -106,6 +106,14 @@ bool is_lua_classname(string_view s);
// Return true if the line of code is a lua comment.
bool is_lua_comment(string_view s);
// Return true if the line of code could be the beginning of a long expression.
// In a read-eval-print loop, if the user types something like "function foo",
// that's not a complete lua expression. But we don't want to just print an error,
// we want to give the user a chance to continue typing so that he can turn it
// into a complete lua expression. This function returns true if the string looks
// like the beginning of a long lua expression. This is only a heuristic.
bool is_possible_long_lua_expression(string_view s);
// Return true if the line is entirely whitespace.
bool is_whitespace(string_view s);

View File

@@ -1071,6 +1071,10 @@ void World::run_scheduled_threads() {
PrettyPrint::Indented().print(LSCO, LuaSpecial(i), &lthread_prints_);
lthread_prints_ << std::endl;
}
if (lthread_prints_.view().empty())
{
lthread_prints_ << "ok\n";
}
}
} else if (status == LUA_YIELD) {
if (is_authoritative()) {

View File

@@ -40,6 +40,7 @@ function cube.lookmenu(add)
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)
end
function sphere.lookhotkeys(add)