diff --git a/data/editor/scripts/plugins/lua_type_defs.lua b/data/editor/scripts/plugins/lua_type_defs.lua index 8eef734df..0e6f4f993 100644 --- a/data/editor/scripts/plugins/lua_type_defs.lua +++ b/data/editor/scripts/plugins/lua_type_defs.lua @@ -1,4 +1,9 @@ local tpl = [[ +export type Vec2 = {number} +export type Vec3 = {number} +export type Color = {number} +export type Quat = {number} +export type DVec3 = {number} declare ImGui: { AlignTextToFramePadding : () -> (), Begin : (string, boolean?) -> (boolean, boolean?), @@ -82,8 +87,8 @@ declare class Entity name : string parent : Entity? rotation : any - position : any - scale : any + position : Vec3 + scale : Vec3 hasComponent : (Entity, any) -> boolean getComponent : (Entity, any) -> any destroy : (Entity) -> () @@ -115,6 +120,7 @@ declare Editor: { } declare LumixAPI: { +%s INPUT_KEYCODE_SHIFT: number, INPUT_KEYCODE_LEFT : number, INPUT_KEYCODE_RIGHT : number, @@ -133,6 +139,12 @@ end declare class FunctionBase end +declare class StructVarBase +end + +declare class StructBase +end + declare class ModuleReflection end @@ -159,6 +171,13 @@ declare LumixReflection: { getFunction : (number) -> FunctionBase, getThisTypeName : (FunctionBase) -> string, getReturnTypeName : (FunctionBase) -> string, + getNumStructs : () -> number, + getStruct : (number) -> StructBase, + getStructName : (StructBase) -> string, + getNumStructMembers : (StructBase) -> number, + getStructMember : (StructBase, number) -> StructVarBase, + getStructMemberName : (StructVarBase) -> string, + getStructMemberType : (StructVarBase) -> number } type InputDevice = { @@ -193,6 +212,7 @@ export type InputEvent = ButtonInputEvent | AxisInputEvent function typeToString(type : number) : string if type < 3 then return "number" end if type == 3 then return "Entity" end + if type == 5 then return "Vec3" end if type == 8 then return "string" end if type == 9 then return "boolean" end if type == 10 then return "string" end @@ -224,9 +244,21 @@ export type InputEvent = ButtonInputEvent | AxisInputEvent end function toLuaType(ctype : string) + if string.match(ctype, "^struct Lumix::") then + ctype = string.sub(ctype, 15) + end if ctype == "int" then return "number" end if ctype == "const char *" then return "string" end + if ctype == "const char*" then return "string" end if ctype == "char const *" then return "string" end + if ctype == "Vec3" then return "Vec3" end + if ctype == "Quat" then return "Quat" end + if ctype == "Vec2" then return "Vec2" end + if ctype == "Color" then return "Color" end + if ctype == "DVec3" then return "DVec3" end + if ctype == "EntityPtr" then return "Entity?" end + if ctype == "EntityRef" then return "Entity" end + if ctype == "Path" then return "string" end if ctype == "i32" then return "number" end if ctype == "u32" then return "number" end if ctype == "float" then return "number" end @@ -237,8 +269,36 @@ export type InputEvent = ButtonInputEvent | AxisInputEvent return `any --[[{ctype}]]` end + function memberTypeToString(type : number) : string + if type == 2 then return "boolean" end + if type == 3 then return "number" end + if type == 4 then return "number" end + if type == 5 then return "number" end + if type == 6 then return "string" end + if type == 7 then return "Entity" end + if type == 9 then return "Vec3" end + return "any" + end + function refl() local out = "" + local lumixAPI_src = "" + local num_structs = LumixReflection.getNumStructs() + for i = 0, num_structs - 1 do + local struct = LumixReflection.getStruct(i) + local name = LumixReflection.getStructName(struct) + out = out .. `declare class {name}\n` + local num_members = LumixReflection.getNumStructMembers(struct) + lumixAPI_src = lumixAPI_src .. `\t{name} : \{ create : () -> {name}, destroy : ({name}) -> () \},\n` + for j = 0, num_members - 1 do + local member = LumixReflection.getStructMember(struct, j) + local member_name = LumixReflection.getStructMemberName(member) + local type = LumixReflection.getStructMemberType(member) + out = out .. `\t{member_name} : {memberTypeToString(type)}\n` + end + out = out .. `end\n\n` + end + local num_funcs = LumixReflection.getNumFunctions() for i = 1, num_funcs do local fn = LumixReflection.getFunction(i - 1) @@ -295,12 +355,12 @@ export type InputEvent = ButtonInputEvent | AxisInputEvent out = out .. "end\n\n" end - return string.format(tpl, world_src, out, entity_src) + return string.format(tpl, world_src, out, entity_src, lumixAPI_src) end local type_defs = refl() -if false then +if true then return { name = "Lua type defs", gui = function() diff --git a/data/scripts/lumix.d.lua b/data/scripts/lumix.d.lua index ceca63d71..327f4e17b 100644 --- a/data/scripts/lumix.d.lua +++ b/data/scripts/lumix.d.lua @@ -1,3 +1,8 @@ +export type Vec2 = {number} +export type Vec3 = {number} +export type Color = {number} +export type Quat = {number} +export type DVec3 = {number} declare ImGui: { AlignTextToFramePadding : () -> (), Begin : (string, boolean?) -> (boolean, boolean?), @@ -82,19 +87,31 @@ declare class World end +declare class RaycastHit + position : Vec3 + normal : Vec3 + entity : Entity +end + +declare class SweepHit + position : Vec3 + normal : Vec3 + entity : Entity +end + declare class GUISystem enableCursor : (GUISystem, boolean) -> () end declare class AssetBrowser - openEditor : (AssetBrowser, any --[[const char*]]) -> () + openEditor : (AssetBrowser, string) -> () end declare class SceneView - getViewportRotation : (SceneView) -> any --[[struct Lumix::Quat]] - setViewportRotation : (SceneView, any --[[Quat]]) -> () - getViewportPosition : (SceneView) -> any --[[struct Lumix::DVec3]] - setViewportPosition : (SceneView, any --[[DVec3]]) -> () + getViewportRotation : (SceneView) -> Quat + setViewportRotation : (SceneView, Quat) -> () + getViewportPosition : (SceneView) -> DVec3 + setViewportPosition : (SceneView, DVec3) -> () end declare class Model @@ -113,9 +130,9 @@ declare class animation_module end declare class gui_module - getRectAt : (gui_module, any --[[Vec2]]) -> any --[[struct Lumix::EntityPtr]] - isOver : (gui_module, any --[[Vec2]], any --[[EntityPtr]]) -> boolean - getSystem : (gui_module) -> GUISystem + getRectAt : (gui_module, Vec2) -> Entity? + isOver : (gui_module, Vec2, Entity?) -> boolean + getSystem : (gui_module) -> any --[[GUISystem *]] end declare class lua_script_module @@ -123,7 +140,7 @@ end declare class audio_module setMasterVolume : (audio_module, number) -> () - play : (audio_module, any --[[EntityPtr]], any --[[const char*]], boolean) -> number + play : (audio_module, Entity?, string, boolean) -> number stop : (audio_module, number) -> () isEnd : (audio_module, number) -> boolean setFrequency : (audio_module, number, number) -> () @@ -132,16 +149,17 @@ declare class audio_module end declare class renderer_module - addDebugCross : (renderer_module, any --[[DVec3]], number, any --[[Color]]) -> () - addDebugLine : (renderer_module, any --[[DVec3]], any --[[DVec3]], any --[[Color]]) -> () - addDebugTriangle : (renderer_module, any --[[DVec3]], any --[[DVec3]], any --[[DVec3]], any --[[Color]]) -> () - setActiveCamera : (renderer_module, any --[[EntityPtr]]) -> () + addDebugCross : (renderer_module, DVec3, number, Color) -> () + addDebugLine : (renderer_module, DVec3, DVec3, Color) -> () + addDebugTriangle : (renderer_module, DVec3, DVec3, DVec3, Color) -> () + setActiveCamera : (renderer_module, Entity?) -> () end declare class physics_module - raycast : (physics_module, any --[[Vec3]], any --[[Vec3]], number, any --[[EntityPtr]]) -> any --[[struct Lumix::EntityPtr]] - sweepSphere : (physics_module, any --[[DVec3]], number, any --[[Vec3]], number, any --[[EntityPtr]], number) -> any --[[struct Lumix::EntityPtr]] - setGravity : (physics_module, any --[[Vec3]]) -> () + raycast : (physics_module, Vec3, Vec3, number, Entity?) -> Entity? + raycastEx : (physics_module, Vec3, Vec3, number, any --[[void*]], Entity?, number) -> boolean + sweepSphere : (physics_module, DVec3, number, Vec3, number, any --[[void*]], Entity?, number) -> boolean + setGravity : (physics_module, Vec3) -> () end declare class spline_component @@ -177,7 +195,7 @@ declare class terrain_component tesselation: number grid_resolution: number grass: any - getTerrainNormalAt : (terrain_component, number, number) -> any --[[struct Lumix::Vec3]] + getTerrainNormalAt : (terrain_component, number, number) -> Vec3 getTerrainHeightAt : (terrain_component, number, number) -> number end @@ -191,7 +209,7 @@ end declare class decal_component material: string - half_extents: any + half_extents: Vec3 uv_scale: any end @@ -207,12 +225,12 @@ declare class point_light_component intensity: number fov: number attenuation: number - color: any + color: Vec3 range: number end declare class environment_component - color: any + color: Vec3 intensity: number indirect_intensity: number shadow_cascades: any @@ -228,19 +246,19 @@ declare class model_instance_component enabled: boolean material: string source: string - getModel : (model_instance_component) -> Model + getModel : (model_instance_component) -> any --[[Model *]] end declare class environment_probe_component enabled: boolean - inner_range: any - outer_range: any + inner_range: Vec3 + outer_range: Vec3 end declare class reflection_probe_component enabled: boolean size: number - half_extents: any + half_extents: Vec3 end declare class fur_component @@ -256,8 +274,8 @@ end declare class bone_attachment_component parent: Entity - relative_position: any - relative_rotation: any + relative_position: Vec3 + relative_rotation: Vec3 bone: number end @@ -272,10 +290,10 @@ declare class rigid_actor_component material: string putToSleep : (rigid_actor_component) -> () getSpeed : (rigid_actor_component) -> number - getVelocity : (rigid_actor_component) -> any --[[struct Lumix::Vec3]] - applyForce : (rigid_actor_component, any --[[Vec3]]) -> () - applyImpulse : (rigid_actor_component, any --[[Vec3]]) -> () - addForceAtPos : (rigid_actor_component, any --[[Vec3]], any --[[Vec3]]) -> () + getVelocity : (rigid_actor_component) -> Vec3 + applyForce : (rigid_actor_component, Vec3) -> () + applyImpulse : (rigid_actor_component, Vec3) -> () + addForceAtPos : (rigid_actor_component, Vec3, Vec3) -> () end declare class physical_heightfield_component @@ -292,14 +310,14 @@ declare class physical_controller_component use_root_motion: boolean use_custom_gravity: boolean custom_gravity_acceleration: number - move : (physical_controller_component, any --[[Vec3]]) -> () + move : (physical_controller_component, Vec3) -> () isCollisionDown : (physical_controller_component) -> boolean getGravitySpeed : (physical_controller_component) -> number end declare class lua_script_component scripts: any - getScriptPath : (lua_script_component, number) -> any --[[struct Lumix::Path]] + getScriptPath : (lua_script_component, number) -> string end declare class gui_image_component @@ -331,7 +349,7 @@ end declare class distance_joint_component connected_body: Entity - axis_position: any + axis_position: Vec3 damping: number stiffness: number tolerance: number @@ -340,8 +358,8 @@ end declare class hinge_joint_component connected_body: Entity - axis_position: any - axis_direction: any + axis_position: Vec3 + axis_direction: Vec3 damping: number stiffness: number use_limit: boolean @@ -350,16 +368,16 @@ end declare class spherical_joint_component connected_body: Entity - axis_position: any - axis_direction: any + axis_position: Vec3 + axis_direction: Vec3 use_limit: boolean limit: any end declare class d6_joint_component connected_body: Entity - axis_position: any - axis_direction: any + axis_position: Vec3 + axis_direction: Vec3 x_motion: number y_motion: number z_motion: number @@ -377,7 +395,7 @@ declare class vehicle_component current_gear: number rpm: number mass: number - center_of_mass: any + center_of_mass: Vec3 moi_multiplier: number chassis: string chassis_layer: number @@ -406,13 +424,13 @@ declare class navmesh_agent_component move_entity: boolean speed: number setActive : (navmesh_agent_component, boolean) -> () - navigate : (navmesh_agent_component, any --[[DVec3]], number, number) -> boolean + navigate : (navmesh_agent_component, DVec3, number, number) -> boolean cancelNavigation : (navmesh_agent_component) -> () drawPath : (navmesh_agent_component) -> () end declare class navmesh_zone_component - extents: any + extents: Vec3 agent_height: number agent_radius: number cell_size: number @@ -423,10 +441,10 @@ declare class navmesh_zone_component detailed: boolean load : (navmesh_zone_component) -> boolean drawContours : (navmesh_zone_component) -> () - drawNavmesh : (navmesh_zone_component, any --[[DVec3]], boolean, boolean, boolean) -> () + drawNavmesh : (navmesh_zone_component, DVec3, boolean, boolean, boolean) -> () drawCompactHeightfield : (navmesh_zone_component) -> () drawHeightfield : (navmesh_zone_component) -> () - generateNavmesh : (navmesh_zone_component) -> any --[[struct Lumix::NavmeshBuildJob *]] + generateNavmesh : (navmesh_zone_component) -> any --[[NavmeshBuildJob *]] end declare class lua_script_inline_component @@ -447,11 +465,11 @@ declare class animator_component use_root_motion: boolean setFloatInput : (animator_component, number, number) -> () setBoolInput : (animator_component, number, boolean) -> () - getInputIndex : (animator_component, any --[[const char*]]) -> number + getInputIndex : (animator_component, string) -> number end declare class physical_instanced_cube_component - half_extents: any + half_extents: Vec3 layer: number end @@ -486,8 +504,8 @@ declare class Entity name : string parent : Entity? rotation : any - position : any - scale : any + position : Vec3 + scale : Vec3 hasComponent : (Entity, any) -> boolean getComponent : (Entity, any) -> any destroy : (Entity) -> () @@ -563,6 +581,9 @@ declare Editor: { } declare LumixAPI: { + RaycastHit : { create : () -> RaycastHit, destroy : (RaycastHit) -> () }, + SweepHit : { create : () -> SweepHit, destroy : (SweepHit) -> () }, + INPUT_KEYCODE_SHIFT: number, INPUT_KEYCODE_LEFT : number, INPUT_KEYCODE_RIGHT : number, @@ -581,6 +602,12 @@ end declare class FunctionBase end +declare class StructVarBase +end + +declare class StructBase +end + declare class ModuleReflection end @@ -607,6 +634,13 @@ declare LumixReflection: { getFunction : (number) -> FunctionBase, getThisTypeName : (FunctionBase) -> string, getReturnTypeName : (FunctionBase) -> string, + getNumStructs : () -> number, + getStruct : (number) -> StructBase, + getStructName : (StructBase) -> string, + getNumStructMembers : (StructBase) -> number, + getStructMember : (StructBase, number) -> StructVarBase, + getStructMemberName : (StructVarBase) -> string, + getStructMemberType : (StructVarBase) -> number } type InputDevice = { diff --git a/data/scripts/math.lua b/data/scripts/math.lua index 6d748424a..6af03bbd3 100644 --- a/data/scripts/math.lua +++ b/data/scripts/math.lua @@ -1,47 +1,47 @@ return { -dot = function(a, b) - return a[1] * b[1] + a[2] * b[2] + a[3] * b[3] -end, - -mulQuat = function(a, b) - return { - a[4] * b[1] + b[4] * a[1] + a[2] * b[3] - b[2] * a[3], - a[4] * b[2] + b[4] * a[2] + a[3] * b[1] - b[3] * a[1], - a[4] * b[3] + b[4] * a[3] + a[1] * b[2] - b[1] * a[2], - a[4] * b[4] - a[1] * b[1] - a[2] * b[2] - a[3] * b[3] - } -end, - -makeQuatFromYaw = function(yaw) - local syaw = math.sin(yaw * 0.5) - local cyaw = math.cos(yaw * 0.5) - return {0, syaw, 0, cyaw } -end, - -makeQuatFromPitch = function(pitch) - local spitch = math.sin(pitch * 0.5) - local cpitch = math.cos(pitch * 0.5) - return {-spitch, 0, 0, cpitch} -end, - -yawToDir = function(yaw) - return {math.sin(yaw), 0, math.cos(yaw)} -end, - -mulVec3Num = function(v, f) - return {v[1] * f, v[2] * f, v[3] * f} -end, - -addVec3 = function(a, b) - return {a[1] + b[1], a[2] + b[2], a[3] + b[3]} -end, - -subVec3 = function(a, b) - return {a[1] - b[1], a[2] - b[2], a[3] - b[3]} -end, - -mulVec3 = function(a, f) - return {a[1] * f, a[2] * f, a[3] * f} -end, + dot = function(a, b) + return a[1] * b[1] + a[2] * b[2] + a[3] * b[3] + end, + + mulQuat = function(a, b) + return { + a[4] * b[1] + b[4] * a[1] + a[2] * b[3] - b[2] * a[3], + a[4] * b[2] + b[4] * a[2] + a[3] * b[1] - b[3] * a[1], + a[4] * b[3] + b[4] * a[3] + a[1] * b[2] - b[1] * a[2], + a[4] * b[4] - a[1] * b[1] - a[2] * b[2] - a[3] * b[3] + } + end, + + makeQuatFromYaw = function(yaw) + local syaw = math.sin(yaw * 0.5) + local cyaw = math.cos(yaw * 0.5) + return {0, syaw, 0, cyaw } + end, + + makeQuatFromPitch = function(pitch) + local spitch = math.sin(pitch * 0.5) + local cpitch = math.cos(pitch * 0.5) + return {-spitch, 0, 0, cpitch} + end, + + yawToDir = function(yaw) + return {math.sin(yaw), 0, math.cos(yaw)} + end, + + mulVec3Num = function(v, f) + return {v[1] * f, v[2] * f, v[3] * f} + end, + + addVec3 = function(a, b) + return {a[1] + b[1], a[2] + b[2], a[3] + b[3]} + end, + + subVec3 = function(a, b) + return {a[1] - b[1], a[2] - b[2], a[3] - b[3]} + end, + + mulVec3 = function(a, f) + return {a[1] * f, a[2] * f, a[3] * f} + end, } diff --git a/src/editor/property_grid.cpp b/src/editor/property_grid.cpp index 6b12996b3..c36331cb8 100644 --- a/src/editor/property_grid.cpp +++ b/src/editor/property_grid.cpp @@ -591,7 +591,7 @@ struct GridUIVisitor final : reflection::IPropertyVisitor void visit(const reflection::ArrayProperty& prop) override { ImGui::Unindent(); - bool is_root_open = ImGui::TreeNodeEx(prop.name, ImGuiTreeNodeFlags_AllowItemOverlap); + bool is_root_open = ImGui::TreeNodeEx(prop.name, ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_DefaultOpen); if (m_entities.size() > 1) { ImGui::Text("Multi-object editing not supported."); diff --git a/src/editor/utils.cpp b/src/editor/utils.cpp index 343e5eeb8..738873c11 100644 --- a/src/editor/utils.cpp +++ b/src/editor/utils.cpp @@ -436,7 +436,7 @@ struct CodeEditorImpl final : CodeEditor { struct Token { enum Flags { NONE = 0, - UNDERLINE = 1 << 0 + UNDERLINE = 1 << 0, }; u32 from; u32 len; @@ -469,7 +469,8 @@ struct CodeEditorImpl final : CodeEditor { END_GROUP, REMOVE, INSERT, - NEW_LINE + NEW_LINE, + MOVE_LINE }; UndoRecord(IAllocator& allocator) : text(allocator), cursors(allocator) {} @@ -486,6 +487,21 @@ struct CodeEditorImpl final : CodeEditor { void execute(CodeEditorImpl& editor, bool is_redo) { ++editor.m_version; switch(type) { + case MOVE_LINE: { + if (from.line < to.line) { + for (i32 line = from.line; line < to.line; ++line) { + swap(editor.m_lines[line], editor.m_lines[line + 1]); + } + } + else { + for (i32 line = from.line; line > to.line; --line) { + swap(editor.m_lines[line], editor.m_lines[line - 1]); + } + } + editor.invalidateTokens(from.line); + editor.invalidateTokens(to.line); + break; + } case BEGIN_GROUP: if (!is_redo) editor.m_cursors.copyTo(cursors); break; @@ -526,6 +542,21 @@ struct CodeEditorImpl final : CodeEditor { void undo(CodeEditorImpl& editor) { ++editor.m_version; switch(type) { + case MOVE_LINE: { + if (to.line < from.line) { + for (i32 line = to.line; line < from.line; ++line) { + swap(editor.m_lines[line], editor.m_lines[line + 1]); + } + } + else { + for (i32 line = to.line; line > from.line; --line) { + swap(editor.m_lines[line], editor.m_lines[line - 1]); + } + } + editor.invalidateTokens(from.line); + editor.invalidateTokens(to.line); + break; + } case BEGIN_GROUP: cursors.copyTo(editor.m_cursors); editor.ensurePointVisible(editor.m_cursors[0]); @@ -659,6 +690,23 @@ struct CodeEditorImpl final : CodeEditor { return (u32)ImGui::CalcTextSize(str, str + cursor.col).x; } + Token getToken(TextPoint p) { + const Line& line = m_lines[p.line]; + for (Token token : line.tokens) { + if (p.col >= (i32)token.from && p.col < i32(token.from + token.len)) return token; + } + return {}; + } + + StringView toStringView(const Token& token, u32 line) { + const char* str = m_lines[line].value.c_str(); + StringView res; + res.begin = str + token.from; + res.end = res.begin + token.len; + return res; + } + + void cursorMoved(Cursor& cursor, bool update_virtual) { cursor.line = clamp(cursor.line, 0, m_lines.size() - 1); cursor.col = clamp(cursor.col, 0, m_lines[cursor.line].length()); @@ -668,6 +716,8 @@ struct CodeEditorImpl final : CodeEditor { } if (update_virtual) cursor.virtual_x = computeCursorX(cursor); m_blink_timer = 0; + m_time_since_cursor_moved = 0; + m_highlighted_str = {}; } void moveCursorLeft(Cursor& cursor, bool word) { @@ -686,7 +736,7 @@ struct CodeEditorImpl final : CodeEditor { void ensurePointVisible(TextPoint& cursor, bool center = false) { if (center) { - m_scroll_y = (cursor.line - (m_last_visible_line - m_first_visible_line) / 2) * ImGui::GetTextLineHeight(); + m_scroll_y += (cursor.line - (m_first_visible_line + m_max_visible_lines / 2)) * ImGui::GetTextLineHeight(); m_scroll_y = maximum(m_scroll_y, 0.f); return; } @@ -700,6 +750,47 @@ struct CodeEditorImpl final : CodeEditor { } } + void moveLinesUp() { + TextPoint from = m_cursors[0]; + TextPoint to = m_cursors[0].sel; + if (from > to) swap(from, to); + if (to.col == 0 && to.line > from.line) --to.line; + if (from.line == 0) return; + + beginUndoGroup(); + m_cursors.resize(1); + UndoRecord& r = pushUndo(); + r.type = UndoRecord::MOVE_LINE; + r.from = from; + r.to = to; + --r.from.line; + r.execute(*this, false); + --m_cursors[0].line; + --m_cursors[0].sel.line; + endUndoGroup(); + } + + void moveLinesDown() { + m_cursors.resize(1); + TextPoint from = m_cursors[0]; + TextPoint to = m_cursors[0].sel; + if (from > to) swap(from, to); + if (to.col == 0 && to.line > from.line) --to.line; + if (to.line == m_lines.size() - 1) return; + + beginUndoGroup(); + m_cursors.resize(1); + UndoRecord& r = pushUndo(); + r.type = UndoRecord::MOVE_LINE; + r.from = to; + r.to = from; + ++r.from.line; + r.execute(*this, false); + ++m_cursors[0].line; + ++m_cursors[0].sel.line; + endUndoGroup(); + } + void moveCursorUp(Cursor& cursor, u32 line_count = 1) { const char* line_str = m_lines[cursor.line].value.c_str(); cursor.line = maximum(0, cursor.line - line_count); @@ -755,15 +846,13 @@ struct CodeEditorImpl final : CodeEditor { void moveCursorBegin(Cursor& cursor, bool doc) { if (doc) cursor.line = 0; - if (cursor.col == 0) { - const String& line = m_lines[cursor.line].value; - while (cursor.col < (i32)line.length() && !isWordChar(line[cursor.col])) { - ++cursor.col; - } - } - else { - cursor.col = 0; + const i32 prev_col = cursor.col; + const String& line = m_lines[cursor.line].value; + cursor.col = 0; + while (cursor.col < (i32)line.length() && !isWordChar(line[cursor.col])) { + ++cursor.col; } + if (cursor.col >= prev_col && prev_col != 0) cursor.col = 0; cursorMoved(cursor, true); if (&cursor == &m_cursors[0]) ensurePointVisible(cursor); } @@ -1026,7 +1115,7 @@ struct CodeEditorImpl final : CodeEditor { new_cursor.sel.col = i32(found - m_lines[line].value.c_str()); new_cursor.col = new_cursor.sel.col + sel_view.size(); new_cursor.virtual_x = computeCursorX(new_cursor); - ensurePointVisible(new_cursor, true); + ensurePointVisible(new_cursor); return; } ++line; @@ -1097,25 +1186,30 @@ struct CodeEditorImpl final : CodeEditor { TextPoint getLeftWord(TextPoint point) { TextPoint p = getLeft(point); - bool is_word = isWordChar(getChar(p)); - p = getLeft(p); - - while (isWordChar(getChar(p)) == is_word) { + if (p.col == 0) return p; + const bool is_word = isWordChar(getChar(p)); + for (;;) { p = getLeft(p); - if (p.line == 0 && p.col == 0) return p; + char c = getChar(p); + if (isWordChar(c) != is_word) { + p = getRight(p); + return p; + } + if (p.col == 0) return p; } - return getRight(p); + return p; } TextPoint getRightWord(TextPoint point) { - TextPoint p = getRight(point); - bool is_word = isWordChar(getChar(p)); - p = getRight(p); - - while (isWordChar(getChar(p)) == is_word) { + TextPoint p = point; + const bool is_word = isWordChar(getChar(p)); + char c; + do { p = getRight(p); + c = getChar(p); + if (c == '\n') return p; if (p.line == m_lines.size() - 1 && p.col == m_lines.back().length()) return p; - } + } while (isWordChar(c) == is_word); return p; } @@ -1450,9 +1544,9 @@ struct CodeEditorImpl final : CodeEditor { // text m_first_visible_line = i32(m_scroll_y / line_height); - float visible_lines = content_size.y / line_height; + m_max_visible_lines = i32(content_size.y / line_height); m_first_visible_line = clamp(m_first_visible_line, 0, m_lines.size() - 1); - m_last_visible_line = minimum(m_first_visible_line + i32(visible_lines), m_lines.size() - 1); + m_last_visible_line = minimum(m_first_visible_line + i32(m_max_visible_lines), m_lines.size() - 1); { PROFILE_BLOCK("tokenize"); @@ -1468,10 +1562,16 @@ struct CodeEditorImpl final : CodeEditor { const char* str = m_lines[j].value.c_str(); ImVec2 p = text_area_pos + ImVec2(0, line_offset_y); for (const Token& t : m_lines[j].tokens) { - dl->AddText(p, m_token_colors[t.type], str + t.from, str + t.from + t.len); + ImVec2 start_p = p; p.x += ImGui::CalcTextSize(str + t.from, str + t.from + t.len).x; + if (equalStrings(toStringView(t, j), m_highlighted_str)) { + dl->AddRectFilled(start_p, p + ImVec2(0, line_height), IM_COL32(0x50, 0x50, 0x50, 0x7f)); + } + + dl->AddText(start_p, m_token_colors[t.type], str + t.from, str + t.from + t.len); + if (t.flags & Token::UNDERLINE) { if (m_handle_input && ImGui::IsMouseHoveringRect(start_p, p + ImVec2(0, line_height))) { const Underline* underline = getUnderline(j, t); @@ -1485,6 +1585,19 @@ struct CodeEditorImpl final : CodeEditor { profiler::pushInt("Num tokens", visible_tokens); // cursors + float prev_since_cursor_moved = m_time_since_cursor_moved; + m_time_since_cursor_moved += io.DeltaTime; + if (m_time_since_cursor_moved > 0.5f && prev_since_cursor_moved <= 0.5f) { + m_highlighted_str = toStringView(getToken(m_cursors[0]), m_cursors[0].line); + bool empty = true; + for (const char* c = m_highlighted_str.begin; c != m_highlighted_str.end; ++c) { + if (isWordChar(*c)) { + empty = false; + break; + } + } + if (empty) m_highlighted_str = {}; + } m_blink_timer += io.DeltaTime; m_blink_timer = fmodf(m_blink_timer, 1.f); bool draw_cursors = m_blink_timer < 0.6f; @@ -1495,8 +1608,8 @@ struct CodeEditorImpl final : CodeEditor { if (draw_cursors) dl->AddRectFilled(cursor_pos, cursor_pos + ImVec2(1, line_height), code_color); if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) moveCursorLeft(c, io.KeyCtrl); else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) moveCursorRight(c, io.KeyCtrl); - else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) moveCursorUp(c); - else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) moveCursorDown(c); + else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) io.KeyAlt ? moveLinesUp() : moveCursorUp(c); + else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) io.KeyAlt ? moveLinesDown() : moveCursorDown(c); else if (ImGui::IsKeyPressed(ImGuiKey_End)) moveCursorEnd(c, io.KeyCtrl); else if (ImGui::IsKeyPressed(ImGuiKey_Home)) moveCursorBegin(c, io.KeyCtrl); } @@ -1658,6 +1771,8 @@ struct CodeEditorImpl final : CodeEditor { i32 m_first_untokenized_line = 0; TagAllocator m_allocator; float m_blink_timer = 0; + float m_time_since_cursor_moved = 0; + StringView m_highlighted_str; StudioApp& m_app; Array m_lines; Array m_underlines; @@ -1665,6 +1780,7 @@ struct CodeEditorImpl final : CodeEditor { Array m_cursors; i32 m_first_visible_line = 0; i32 m_last_visible_line = 0; + i32 m_max_visible_lines = 0; Tokenizer m_tokenizer = nullptr; Span m_token_colors; u32 m_version = 0; diff --git a/src/engine/lua_api.cpp b/src/engine/lua_api.cpp index 4a316c3fa..e5bc11e76 100644 --- a/src/engine/lua_api.cpp +++ b/src/engine/lua_api.cpp @@ -410,10 +410,38 @@ static i32 LUA_getNumFunctions() { return reflection::allFunctions().size(); } +static i32 LUA_getNumStructs() { + return reflection::allStructs().size(); +} + static reflection::FunctionBase* LUA_getFunction(i32 idx) { return reflection::allFunctions()[idx]; } +static reflection::StructBase* LUA_getStruct(i32 idx) { + return reflection::allStructs()[idx]; +} + +static const char* LUA_getStructName(reflection::StructBase* str) { + return str->name; +} + +static i32 LUA_getNumStructMembers(reflection::StructBase* str) { + return str->members.size(); +} + +static reflection::StructVarBase* LUA_getStructMember(reflection::StructBase* str, u32 idx) { + return str->members[idx]; +} + +static u32 LUA_getStructMemberType(reflection::StructVarBase* var) { + return (u32)var->getType().type; +} + +static const char* LUA_getStructMemberName(reflection::StructVarBase* var) { + return var->name; +} + static i32 LUA_getNextModule(lua_State* L) { reflection::Module* module = LuaWrapper::checkArg(L, 1); if (module->next) lua_pushlightuserdata(L, module->next); @@ -953,6 +981,14 @@ void registerEngineAPI(lua_State* L, Engine* engine) LuaWrapper::createSystemFunction(L, "LumixReflection", "getNumFunctions", &LuaWrapper::wrap); LuaWrapper::createSystemFunction(L, "LumixReflection", "getFunction", &LuaWrapper::wrap); + LuaWrapper::createSystemFunction(L, "LumixReflection", "getNumStructs", &LuaWrapper::wrap); + LuaWrapper::createSystemFunction(L, "LumixReflection", "getStruct", &LuaWrapper::wrap); + LuaWrapper::createSystemFunction(L, "LumixReflection", "getStructName", &LuaWrapper::wrap); + LuaWrapper::createSystemFunction(L, "LumixReflection", "getNumStructMembers", &LuaWrapper::wrap); + LuaWrapper::createSystemFunction(L, "LumixReflection", "getStructMember", &LuaWrapper::wrap); + LuaWrapper::createSystemFunction(L, "LumixReflection", "getStructMemberName", &LuaWrapper::wrap); + LuaWrapper::createSystemFunction(L, "LumixReflection", "getStructMemberType", &LuaWrapper::wrap); + LuaWrapper::createSystemFunction(L, "LumixAPI", "networkRead", &LUA_networkRead); LuaWrapper::createSystemFunction(L, "LumixAPI", "packU32", &LUA_packU32); LuaWrapper::createSystemFunction(L, "LumixAPI", "unpackU32", &LUA_unpackU32); diff --git a/src/engine/reflection.cpp b/src/engine/reflection.cpp index 3498a1dde..083c06663 100644 --- a/src/engine/reflection.cpp +++ b/src/engine/reflection.cpp @@ -38,6 +38,11 @@ Array& allFunctions() { return fncs; } +Array& allStructs() { + static Array structs(getGlobalAllocator()); + return structs; +} + ComponentBase::ComponentBase(IAllocator& allocator) : props(allocator) , functions(allocator) diff --git a/src/engine/reflection.h b/src/engine/reflection.h index 7be8e250d..ed9a48709 100644 --- a/src/engine/reflection.h +++ b/src/engine/reflection.h @@ -326,7 +326,7 @@ StringView getTypeName() struct Variant { Variant() { type = I32; i = 0; } - enum Type { + enum Type : u32 { VOID, PTR, BOOL, @@ -380,6 +380,7 @@ struct TypeDescriptor { template struct VariantTag {}; template inline Variant::Type _getVariantType(VariantTag) { return Variant::PTR; } +template inline Variant::Type _getVariantType(VariantTag) { return Variant::PTR; } inline Variant::Type _getVariantType(VariantTag) { return Variant::VOID; } inline Variant::Type _getVariantType(VariantTag) { return Variant::BOOL; } inline Variant::Type _getVariantType(VariantTag) { return Variant::I32; } @@ -435,6 +436,7 @@ inline EntityPtr fromVariant(int i, Span args, VariantTag) { inline EntityRef fromVariant(int i, Span args, VariantTag) { return (EntityRef)args[i].e; } inline void* fromVariant(int i, Span args, VariantTag) { return args[i].ptr; } template inline T* fromVariant(int i, Span args, VariantTag) { return (T*)args[i].ptr; } +template inline T& fromVariant(int i, Span args, VariantTag) { return *(T*)args[i].ptr; } template struct VariantCaller { @@ -566,7 +568,75 @@ struct Function : FunctionBase } }; + +struct StructVarBase { + virtual ~StructVarBase() {} + virtual bool set(void* obj, Span mem) = 0; + virtual bool get(const void* obj, Span mem) = 0; + + template T get(void* obj) { + T res; + get(obj, Span((u8*)&res, sizeof(res))); + return res; + } + + template void set(void* obj, T val) { + set(obj, Span((const u8*)&val, sizeof(val))); + } + + virtual TypeDescriptor getType() const = 0; + + const char* name; +}; + +template +struct StructVar : StructVarBase { + using T = typename ResultOf::Type; + using C = typename ClassOf::Type; + + TypeDescriptor getType() const override { + return toTypeDescriptor(); + } + + bool set(void* obj, Span mem) override { + C* inst = (C*)obj; + auto& v = inst->*Getter; + if (sizeof(v) != mem.length()) return false; + memcpy(&v, mem.begin(), sizeof(v)); + return true; + } + + bool get(const void* obj, Span mem) override { + C* inst = (C*)obj; + auto& v = inst->*Getter; + if (sizeof(v) != mem.length()) return false; + memcpy(mem.begin(), &v, sizeof(v)); + return true; + } +}; + +struct StructBase { + StructBase() : allocator(getGlobalAllocator()), members(getGlobalAllocator()) {} + + virtual ~StructBase() {} + virtual void* createInstance(IAllocator& allocator) = 0; + virtual void destroyInstance(void* obj, IAllocator& allocator) = 0; + + template + StructBase& member(const char* name) { + StructVar* member = LUMIX_NEW(allocator, StructVar); + member->name = name; + members.push(member); + return *this; + } + + IAllocator& allocator; + const char* name; + Array members; +}; + LUMIX_ENGINE_API Array& allFunctions(); +LUMIX_ENGINE_API Array& allStructs(); template auto& function(F func, const char* decl_code, const char* name) @@ -579,6 +649,18 @@ auto& function(F func, const char* decl_code, const char* name) return ret; } +template +auto& structure(const char* name) +{ + static struct : StructBase { + void* createInstance(IAllocator& allocator) override { return LUMIX_NEW(allocator, S); } + void destroyInstance(void* obj, IAllocator& allocator) override { LUMIX_DELETE(allocator, (S*)obj); } + } ret; + ret.name = name; + allStructs().push(&ret); + return ret; +} + struct LUMIX_ENGINE_API ComponentBase { ComponentBase(IAllocator& allocator); diff --git a/src/engine/world.cpp b/src/engine/world.cpp index 80f4d3799..1b105fa9d 100644 --- a/src/engine/world.cpp +++ b/src/engine/world.cpp @@ -39,9 +39,10 @@ void EntityMap::set(EntityRef src, EntityRef dst) { m_map[src.index] = dst; } - -World::~World() = default; - +World::~World() { + // release modules first, since they can access world + m_modules.clear(); +} World::World(Engine& engine) : m_allocator(engine.getAllocator(), "world") diff --git a/src/lua_script/lua_script_system.cpp b/src/lua_script/lua_script_system.cpp index 7e3073ea9..e380a7c22 100644 --- a/src/lua_script/lua_script_system.cpp +++ b/src/lua_script/lua_script_system.cpp @@ -124,7 +124,16 @@ static void toVariant(reflection::Variant::Type type, lua_State* L, int idx, ref case reflection::Variant::FLOAT: val = LuaWrapper::checkArg(L, idx); break; case reflection::Variant::ENTITY: val = LuaWrapper::checkArg(L, idx); break; case reflection::Variant::VEC2: val = LuaWrapper::checkArg(L, idx); break; - case reflection::Variant::COLOR: + case reflection::Variant::COLOR: { + if (LuaWrapper::isType(L, idx)) { + Vec4 c = LuaWrapper::toType(L, idx); + val = Color(u8(c.r * 255), u8(c.g * 255), u8(c.b * 255), u8(c.a * 255)); + break; + } + Vec3 c = LuaWrapper::checkArg(L, idx); + val = Color(u8(c.r * 255), u8(c.g * 255), u8(c.b * 255), 0xff); + break; + } case reflection::Variant::VEC3: val = LuaWrapper::checkArg(L, idx); break; case reflection::Variant::DVEC3: val = LuaWrapper::checkArg(L, idx); break; case reflection::Variant::QUAT: val = LuaWrapper::checkArg(L, idx); break; @@ -241,9 +250,129 @@ static int luaCmpMethodClosure(lua_State* L) { return push(L, res, f->getReturnTypeName()); } +static int lua_struct_var_setter(lua_State* L) { + LuaWrapper::checkTableArg(L, 1); // self + const char* prop_name = LuaWrapper::checkArg(L, 2); + int type = lua_getfield(L, 1, "_value"); + if (type != LUA_TLIGHTUSERDATA) luaL_argerror(L, 1, "invalid object"); + void* inst = lua_tolightuserdata(L, -1); + lua_pop(L, 1); + reflection::StructBase* s = LuaWrapper::toType(L, lua_upvalueindex(1)); + + for (reflection::StructVarBase* var : s->members) { + if (equalStrings(var->name, prop_name)) { + reflection::TypeDescriptor td = var->getType(); + switch (td.type) { + case reflection::Variant::DVEC3: { + const DVec3& v = LuaWrapper::checkArg(L, 2); + var->set(inst, v); + return 0; + } + case reflection::Variant::VEC3: { + const Vec3& v = LuaWrapper::checkArg(L, 2); + var->set(inst, v); + return 0; + } + case reflection::Variant::FLOAT: { + const float v = LuaWrapper::checkArg(L, 2); + var->set(inst, v); + return 0; + } + default: + ASSERT(false); + // TODO + return 0; + } + } + } + return 0; +} + +static int lua_struct_var_getter(lua_State* L) { + LuaWrapper::checkTableArg(L, 1); // self + const char* prop_name = LuaWrapper::checkArg(L, 2); + int type = lua_getfield(L, 1, "_value"); + if (type != LUA_TLIGHTUSERDATA) luaL_argerror(L, 1, "invalid object"); + void* inst = lua_tolightuserdata(L, -1); + lua_pop(L, 1); + reflection::StructBase* s = LuaWrapper::toType(L, lua_upvalueindex(1)); + + for (reflection::StructVarBase* var : s->members) { + if (equalStrings(var->name, prop_name)) { + reflection::TypeDescriptor td = var->getType(); + switch (td.type) { + case reflection::Variant::DVEC3: { + LuaWrapper::push(L, var->get(inst)); + return 1; + } + case reflection::Variant::VEC3: { + LuaWrapper::push(L, var->get(inst)); + return 1; + } + case reflection::Variant::FLOAT: { + LuaWrapper::push(L, var->get(inst)); + return 1; + } + default: + ASSERT(false); + // TODO + return 0; + } + } + } + return 0; + } + + static void createClasses(lua_State* L) { LuaWrapper::DebugGuard guard(L); lua_getglobal(L, "LumixAPI"); + for (auto* s : reflection::allStructs()) { + if (LuaWrapper::getField(L, -1, s->name) != LUA_TTABLE) { // [LumixAPI, obj|nil ] + lua_pop(L, 1); // [LumixAPI] + lua_newtable(L); // [LumixAPI, obj] + lua_pushvalue(L, -1); // [LumixAPI, obj, obj] + lua_setfield(L, -3, s->name); // [LumixAPI, obj] + + lua_pushlightuserdata(L, s); // [LumixAPI, obj, refl::struct] + lua_pushcclosure(L, lua_struct_var_getter, "struct_var_getter", 1); // [LumixAPI, obj, var_getter] + lua_setfield(L, -2, "__index"); // [LumixAPI, obj] + + lua_pushlightuserdata(L, s); // [LumixAPI, obj, refl::struct] + lua_pushcclosure(L, lua_struct_var_setter, "struct_var_setter", 1); // [LumixAPI, obj, var_setter] + lua_setfield(L, -2, "__newindex"); // [LumixAPI, obj] + + lua_pushvalue(L, -1); // [LumixAPI, obj, obj] + auto creator = [](lua_State* L) -> int { + auto* s = LuaWrapper::getClosureObject(L); + void* obj = s->createInstance(getGlobalAllocator()); + LuaWrapper::pushObject(L, obj, s->name); + return 1; + }; + + auto destroyer = [](lua_State* L) -> int { + auto* s = LuaWrapper::getClosureObject(L); + LuaWrapper::checkTableArg(L, 1); + void* obj; + if (!LuaWrapper::checkField(L, 1, "_value", &obj)) { + luaL_argerror(L, 1, "expected object"); + } + s->destroyInstance(obj, getGlobalAllocator()); + return 0; + }; + + lua_pushlightuserdata(L, s); // [LumixAPI, obj, obj, refl::struct] + lua_pushcclosure(L, creator, "create", 1); // [LumixAPI, obj, obj, closure] + lua_setfield(L, -2, "create"); // [LumixAPI, obj, obj] + + lua_pushlightuserdata(L, s); // [LumixAPI, obj, obj, refl::struct] + lua_pushcclosure(L, destroyer, "destroy", 1); // [LumixAPI, obj, obj, closure] + lua_setfield(L, -2, "destroy"); // [LumixAPI, obj, obj] + lua_pop(L, 1); // [LumixAPI, obj ] + } + lua_pop(L, 1); + } + for (auto* f : reflection::allFunctions()) { char tmp_obj_type_name[128]; copyString(Span(tmp_obj_type_name), f->getThisTypeName()); diff --git a/src/physics/physics_module.cpp b/src/physics/physics_module.cpp index b4e916776..729b66942 100644 --- a/src/physics/physics_module.cpp +++ b/src/physics/physics_module.cpp @@ -2392,7 +2392,7 @@ struct PhysicsModuleImpl final : PhysicsModule PhysicsModuleImpl* module; }; - EntityPtr sweepSphere(const DVec3& pos, float radius, const Vec3& dir, float distance, EntityPtr ignored, i32 layer) override { + bool sweepSphere(const DVec3& pos, float radius, const Vec3& dir, float distance, SweepHit& result, EntityPtr ignored, i32 layer) override { PxSweepBuffer hit; physx::PxSphereGeometry sphere(radius); physx::PxTransform transform(toPhysx(pos), physx::PxIdentity); @@ -2403,9 +2403,11 @@ struct PhysicsModuleImpl final : PhysicsModule PxQueryFilterData filter_data; filter_data.flags = PxQueryFlag::eDYNAMIC | PxQueryFlag::eSTATIC | PxQueryFlag::ePREFILTER; if (!m_scene->sweep(sphere, transform, toPhysx(dir), distance, hit, physx::PxHitFlag::eDEFAULT, filter_data, &filter)) return INVALID_ENTITY; - if (!hit.hasBlock) return INVALID_ENTITY; - const EntityRef hit_entity = {(int)(intptr_t)hit.block.actor->userData}; - return hit_entity; + if (!hit.hasBlock) return false; + result.entity = EntityPtr{(int)(intptr_t)hit.block.actor->userData}; + result.position = fromPhysx(hit.block.position); + result.normal = fromPhysx(hit.block.normal); + return true; } bool raycastEx(const Vec3& origin, @@ -3961,9 +3963,20 @@ void PhysicsModule::reflect() { return "N/A"; } }; + + reflection::structure("RaycastHit") + .member<&RaycastHit::position>("position") + .member<&RaycastHit::normal>("normal") + .member<&RaycastHit::entity>("entity"); + + reflection::structure("SweepHit") + .member<&SweepHit::position>("position") + .member<&SweepHit::normal>("normal") + .member<&SweepHit::entity>("entity"); LUMIX_MODULE(PhysicsModuleImpl, "physics") .LUMIX_FUNC(raycast) + .LUMIX_FUNC(raycastEx) .LUMIX_FUNC(sweepSphere) .LUMIX_FUNC(setGravity) .LUMIX_CMP(D6Joint, "d6_joint", "Physics / Joint / D6") diff --git a/src/physics/physics_module.h b/src/physics/physics_module.h index f898209c5..b101f0ca7 100644 --- a/src/physics/physics_module.h +++ b/src/physics/physics_module.h @@ -40,13 +40,17 @@ struct World; template struct DelegateList; -struct RaycastHit -{ +struct RaycastHit { Vec3 position; Vec3 normal; EntityPtr entity; }; +struct SweepHit { + Vec3 position; + Vec3 normal; + EntityPtr entity; +}; struct LUMIX_PHYSICS_API PhysicsModule : IModule { @@ -91,7 +95,7 @@ struct LUMIX_PHYSICS_API PhysicsModule : IModule virtual void forceUpdateDynamicActors(float time_delta) = 0; virtual const Array& getDynamicActors() = 0; virtual void render() = 0; - virtual EntityPtr sweepSphere(const DVec3& pos, float radius, const Vec3& dir, float distance, EntityPtr ignored, i32 layer) = 0; + virtual bool sweepSphere(const DVec3& pos, float radius, const Vec3& dir, float distance, SweepHit& result, EntityPtr ignored, i32 layer) = 0; virtual EntityPtr raycast(const Vec3& origin, const Vec3& dir, float distance, EntityPtr ignore_entity) = 0; virtual bool raycastEx(const Vec3& origin, const Vec3& dir, float distance, RaycastHit& result, EntityPtr ignored, int layer) = 0; virtual void setGravity(const Vec3& gravity) = 0;