Skip to content

Commit

Permalink
Merge pull request #5637 from Web-eWorks/lua-bodies-near
Browse files Browse the repository at this point in the history
Add Space.GetBodiesNear(), ProximityQuery module
  • Loading branch information
Webster Sheets authored Jan 9, 2024
2 parents 731b50e + 1a97ea2 commit bf92d37
Show file tree
Hide file tree
Showing 14 changed files with 358 additions and 50 deletions.
2 changes: 1 addition & 1 deletion data/libs/SpaceStation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ function SpaceStation:UnlockAdvert (ref)
end

local function updateSystem ()
local stations = Space.GetBodies(function (b) return b.superType == "STARPORT" end)
local stations = Space.GetBodies("SpaceStation")
for i, station in ipairs(stations) do
-- updateStationMarket(station)
Economy.UpdateStationMarket(station:GetSystemBody())
Expand Down
28 changes: 28 additions & 0 deletions data/meta/Space.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details
-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt

-- This file implements type information about C++ classes for Lua static analysis

---@meta

local Space = {}

package.core["Space"] = Space

-- Returns a list of bodies simulated in the current system, optionally filtered by type
---@generic T
---@param class `T` an optional body classname to filter the returned results by
---@return T[]
---@overload fun(): Body[]
function Space.GetBodies(class) end

-- Returns a list of bodies at most dist away from the given body, optionally filtered by type
---@generic T
---@param body Body the reference body
---@param dist number the maximum distance from the reference body to search
---@param filter `T` an optional body classname to filter the returned results by
---@return T[]
---@overload fun(body: Body, dist: number): Body[]
function Space.GetBodiesNear(body, dist, filter) end

return Space
2 changes: 1 addition & 1 deletion data/modules/Assassination/Assassination.lua
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ local onShipUndocked = function (ship, station)
for ref,mission in pairs(missions) do
if mission.status == 'ACTIVE' and
mission.ship == ship then
planets = Space.GetBodies(function (body) return body:isa("Planet") end)
planets = Space.GetBodies("Planet")
if #planets == 0 then
ship:AIFlyTo(station)
mission.shipstate = 'outbound'
Expand Down
2 changes: 1 addition & 1 deletion data/modules/BulkShips.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ local spawnShips = function ()
return
end

local stations = Space.GetBodies(function (body) return body:isa("SpaceStation") and not body.isGroundStation end)
local stations = utils.filter_array(Space.GetBodies("SpaceStation"), function (body) return not body.isGroundStation end)
if #stations == 0 then
return
end
Expand Down
139 changes: 139 additions & 0 deletions data/modules/Common/ProximityQuery.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details
-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt

local Engine = require 'Engine'
local Space = require 'Space'
local Timer = require 'Timer'
local utils = require 'utils'
local Game = require 'Game'

local ProximityQuery = {}

-- ─── Context ─────────────────────────────────────────────────────────────────

local onBodyEnter = function(ctx, enter) end
local onBodyLeave = function(ctx, leave) end

-- List of bodies currently in proximity is stored with weak keys, so deleted
-- bodies are quietly cleaned up during a GC cycle
local bodyListMt = {
__mode = "k"
}

---@class ProximityQuery.Context
---@field New fun(body, dist, interval, type): self
---@field body Body
---@field dist number
local Context = utils.class 'ProximityQuery.Context'

function Context:Constructor(body, dist, type)
self.bodies = setmetatable({}, bodyListMt)
self.body = body
self.dist = dist
self.filter = type
self.iter = 0

self.onBodyEnter = onBodyEnter
self.onBodyLeave = onBodyLeave
end

function Context:Cancel()
self.iter = nil
end

---@param fn fun(ctx: ProximityQuery.Context, enter: Body)
function Context:SetBodyEnter(fn)
self.onBodyEnter = fn
return self
end

---@param fn fun(ctx: ProximityQuery.Context, leave: Body)
function Context:SetBodyLeave(fn)
self.onBodyLeave = fn
return self
end

-- Class: ProximityQuery
--
-- This class provides a helper utility to allow using Space.GetBodiesNear() in
-- an efficient manner, providing an event-based API when bodies enter or leave
-- a user-specified relevancy range of the reference body.

-- Function: CreateQuery
--
-- Register a new periodic proximity test relative to the given body
--
-- Parameters:
--
-- body - the reference body to perform testing for
-- dist - the distance (in meters) of the proximity test to perform
-- interval - how often a proximity test should be performed in seconds.
-- Smaller values are more performance-hungry.
-- type - optional body classname filter, see Space.GetBodiesNear
-- overlap - if false, all bodies of type in the radius will generate
-- initial proximity events on the first proximity test
--
-- Returns:
--
-- context - the context object for the registered test
--
function ProximityQuery.CreateQuery(body, dist, interval, type, overlap)
local context = Context.New(body, dist, type)
local cb = ProximityQuery.MakeCallback(context)

-- Queue the start of the timer at a random timestamp inside the first interval period
-- This provides natural load balancing for large numbers of callbacks created on the same frame
-- (e.g. at game start / hyperspace entry)
Timer:CallAt(Game.time + Engine.rand:Number(interval), function()

if overlap == false then
cb()
else
-- Pre-fill the list of nearby bodies (avoid spurious onBodyEnter() callbacks when creating)
for i, locBody in ipairs(Space.GetBodiesNear(body, dist, type)) do
context.bodies[locBody] = context.iter
end
end

Timer:CallEvery(interval, cb)
end)

return context
end

---@private
---@param context ProximityQuery.Context
function ProximityQuery.MakeCallback(context)
return function()
-- Callback has been cancelled or query body no longer exists,
-- stop ticking this query
if not context.iter or not context.body:exists() then
return true
end

local newIter = (context.iter + 1) % 2
context.iter = newIter

for i, locBody in ipairs(Space.GetBodiesNear(context.body, context.dist, context.filter)) do
if not context.bodies[locBody] then
context.onBodyEnter(context, locBody)
end

context.bodies[locBody] = newIter
end

local remove = {}
for locBody, ver in pairs(context.bodies) do
if ver ~= newIter then
context.onBodyLeave(context, locBody)
table.insert(remove, locBody)
end
end

for _, v in ipairs(remove) do
context.bodies[v] = nil
end
end
end

return ProximityQuery
15 changes: 8 additions & 7 deletions data/modules/SearchRescue/SearchRescue.lua
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ local triggerAdCreation = function ()
-- Return if ad should be created based on lawlessness and min/max frequency values.
-- Ad number per system is based on how many stations a system has so a player will
-- be met with a certain number of stations that have one or more ads.
local stations = Space.GetBodies(function (body) return body.superType == 'STARPORT' end)
local stations = Space.GetBodies("SpaceStation")
local freq = Game.system.lawlessness * ad_freq_max
if freq < ad_freq_min then freq = ad_freq_min end
local ad_num_max = freq * #stations
Expand Down Expand Up @@ -1001,10 +1001,11 @@ local findNearbyStations = function (vacuum, body)
-- get station bodies within current system depending on vacuum variable
local nearbystations_raw
if vacuum == true then
nearbystations_raw = Space.GetBodies(function (body)
return body.superType == 'STARPORT' and (body.type == 'STARPORT_ORBITAL' or (not body.path:GetSystemBody().parent.hasAtmosphere)) end)
nearbystations_raw = utils.filter_array(Space.GetBodies("SpaceStation"), function (body)
return body.type == 'STARPORT_ORBITAL' or (not body.path:GetSystemBody().parent.hasAtmosphere)
end)
else
nearbystations_raw = Space.GetBodies(function (body) return body.superType == 'STARPORT' end)
nearbystations_raw = Space.GetBodies("SpaceStation")
end

-- determine distance to body
Expand Down Expand Up @@ -1040,7 +1041,7 @@ local findClosestPlanets = function ()
end

-- get planets with stations and remove from planet list
local ground_stations = Space.GetBodies(function (body) return body.type == 'STARPORT_SURFACE' end)
local ground_stations = utils.filter_array(Space.GetBodies("SpaceStation"), function (body) return body.type == 'STARPORT_SURFACE' end)
for _,ground_station in pairs(ground_stations) do
for i=#rockyplanets, 1, -1 do
if rockyplanets[i] == Space.GetBody(ground_station.path:GetSystemBody().parent.index) then
Expand All @@ -1052,7 +1053,7 @@ local findClosestPlanets = function ()

-- create dictionary of stations
local closestplanets = {}
local stations = Space.GetBodies(function (body) return body.superType == 'STARPORT' end)
local stations = Space.GetBodies("SpaceStation")
for _,station in pairs(stations) do closestplanets[station] = {} end

-- pick closest planets to stations
Expand Down Expand Up @@ -1299,7 +1300,7 @@ local makeAdvert = function (station, manualFlavour, closestplanets)
local needed_fuel
if flavour.id == 2 or flavour.id == 5 then
needed_fuel = math.max(math.floor(shipdef.fuelTankMass * 0.1), 1)
elseif flavour.id == 4 then -- different planet
elseif flavour.id == 4 then -- different planet
needed_fuel = math.max(math.floor(shipdef.fuelTankMass * 0.5), 1)
end
deliver_comm[Commodities.hydrogen] = needed_fuel
Expand Down
5 changes: 3 additions & 2 deletions data/modules/System/Explore.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ local Event = require 'Event'
local Comms = require 'Comms'
local Timer = require 'Timer'
local Lang = require 'Lang'
local utils = require 'utils'

local l = Lang.GetResource("module-system")

local exploreSystem = function (system)
Comms.Message(l.GETTING_SENSOR_DATA)
local starports = #Space.GetBodies(function (body) return body.superType == 'STARPORT' end)
local major_bodies = #Space.GetBodies(function (body) return body.superType and body.superType ~= 'STARPORT' and body.superType ~= 'NONE' end)
local starports = #Space.GetBodies("SpaceStation")
local major_bodies = #Space.GetBodies("TerrainBody")
local bodies
if major_bodies == 1 then
bodies = l.BODY
Expand Down
2 changes: 1 addition & 1 deletion data/modules/TradeShips/Debug.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ debugView.registerTab('debug-trade-ships', function()
ui.sameLine()
property("Lawlessness", string.format("%.4f", Game.system.lawlessness))
ui.sameLine()
property("Total bodies in space", #Space.GetBodies())
property("Total bodies in space", Space.GetNumBodies())
end

if ui.collapsingHeader("Stations") then
Expand Down
2 changes: 1 addition & 1 deletion data/modules/TradeShips/Flow.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Flow.calculateSystemParams = function()
if system.population == 0 then return nil end

-- all ports in the system
local ports = Space.GetBodies(function (body) return body.superType == 'STARPORT' end)
local ports = Space.GetBodies("SpaceStation")
-- check if the current system can be traded in
if #ports == 0 then return nil end

Expand Down
31 changes: 31 additions & 0 deletions src/lua/LuaBody.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,31 @@
#include "SpaceStation.h"
#include "Star.h"

// For LuaFlags<ObjectType>
#include "enum_table.h"
#include "pigui/LuaFlags.h"

namespace PiGui {
// Declared in LuaPiGuiInternal.h
extern bool first_body_is_more_important_than(Body *, Body *);
extern int pushOnScreenPositionDirection(lua_State *l, vector3d position);
} // namespace PiGui

static LuaFlags<ObjectType> s_bodyFlags ({
{ "Body", ObjectType::BODY },
{ "ModelBody", ObjectType::MODELBODY },
{ "Ship", ObjectType::SHIP },
{ "Player", ObjectType::PLAYER },
{ "SpaceStation", ObjectType::SPACESTATION },
{ "TerrainBody", ObjectType::TERRAINBODY },
{ "Planet", ObjectType::PLANET },
{ "Star", ObjectType::STAR },
{ "CargoBody", ObjectType::CARGOBODY },
{ "Projectile", ObjectType::PROJECTILE },
{ "Missile", ObjectType::MISSILE },
{ "HyperspaceCloud", ObjectType::HYPERSPACECLOUD }
});

/*
* Class: Body
*
Expand Down Expand Up @@ -843,6 +862,16 @@ static int l_body_set_ang_velocity(lua_State *l)
return 0;
}

void pi_lua_generic_pull(lua_State *l, int index, ObjectType &objectType)
{
objectType = s_bodyFlags.LookupEnum(l, index);
}

void pi_lua_generic_push(lua_State *l, ObjectType bodyType)
{
lua_pushstring(l, EnumStrings::GetString("PhysicsObjectType", static_cast<int>(bodyType)));
}

template <>
const char *LuaObject<Body>::s_type = "Body";

Expand Down Expand Up @@ -908,4 +937,6 @@ void LuaObject<Body>::RegisterClass()
LuaObjectBase::RegisterSerializer("CargoBody", body_serializers);
LuaObjectBase::RegisterSerializer("Missile", body_serializers);
LuaObjectBase::RegisterSerializer("HyperspaceCloud", body_serializers);

s_bodyFlags.Register(Lua::manager->GetLuaState(), "Constants.ObjectType");
}
9 changes: 9 additions & 0 deletions src/lua/LuaBody.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt

#include "Body.h"
#include "Lua.h"
#include "LuaPushPull.h"

void pi_lua_generic_pull(lua_State *l, int index, ObjectType &bodyType);
void pi_lua_generic_push(lua_State *l, ObjectType bodyType);
2 changes: 1 addition & 1 deletion src/lua/LuaPiGui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ static Type parse_imgui_enum(lua_State *l, int index, LuaFlags<Type> lookupTable
return static_cast<Type>(lua_tointeger(l, index));
else {
luaL_traceback(l, l, NULL, 1);
Error("Expected a table or integer, got %s.\n%s\n", luaL_typename(l, index), lua_tostring(l, -1));
Error("Expected a string or integer, got %s.\n%s\n", luaL_typename(l, index), lua_tostring(l, -1));
}
return static_cast<Type>(0);
}
Expand Down
Loading

0 comments on commit bf92d37

Please sign in to comment.