Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster wire rendering #2955

Merged
merged 17 commits into from
Feb 24, 2024
Merged

Conversation

Fasteroid
Copy link
Contributor

It's not perfect, but it's certainly better that what we had before. Cleaned up a bunch of old code that doesn't do anything and added some micro-optimizations where doable (but they didn't have much of an impact in my testing)

The big change with this PR is that entities with stupid amounts of wires will no longer drop your FPS provided they remain stationary. This is thanks to a new LocalToWorld result caching system, which avoids creating new vector objects for repeated calls with the same input vector.

This LocalToWorld cache system is now a part of WireLib so it can be used in other files where applicable. The more it's used, the better the global performance should become.

@Fasteroid Fasteroid force-pushed the faster-wire-rendering branch from 5be50ac to 9b34ace Compare December 24, 2023 14:30
@thegrb93
Copy link
Contributor

The only problem I see is the wire used to render even if ent was Invalid, which I guess would correspond to world entity. If ent is Invalid then it uses world offsets for the wire instead of entity local offsets. We might still want that behavior.

@Fasteroid
Copy link
Contributor Author

The only problem I see is the wire used to render even if ent was Invalid, which I guess would correspond to world entity. If ent is Invalid then it uses world offsets for the wire instead of entity local offsets. We might still want that behavior.

The wire tool itself doesn't even let you attach wires to the world so this probably isn't an issue.

Comment on lines 1220 to 1261
local ents_orientations = {}
local ents_cached_positions = {}

local LocalToWorld = FindMetaTable("Entity").LocalToWorld
local orientation, cur_cached_positions, cur_ent

function WireLib.LocalToWorld_UseEnt(ent)
if cur_ent == ent then return end -- call this as much as you want

orientation = ents_orientations[ent]
cur_cached_positions = ents_cached_positions[ent]
cur_ent = ent

if not orientation or not cur_cached_positions then
ents_orientations[ent] = { ent:GetPos(), ent:GetAngles() }
cur_cached_positions = {}
ents_cached_positions[ent] = cur_cached_positions
return
end

local pos = ent:GetPos()
local ang = ent:GetAngles()

if not rawequal(orientation[1], pos) or not rawequal(orientation[2], ang) then
orientation[1] = pos
orientation[2] = ang
cur_cached_positions = {}
ents_cached_positions[ent] = cur_cached_positions
return
end

op = retrieve
end

function WireLib.LocalToWorld_Find(pos)
local fetch = cur_cached_positions[pos]
if fetch then return fetch
else
cur_cached_positions[pos] = LocalToWorld(cur_ent, pos)
return cur_cached_positions[pos]
end
end
Copy link
Contributor

@thegrb93 thegrb93 Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be generalized to something cleaner like

local ents_computed_data = setmetatable({}, {__index=function(t,k) local r=setmetatable({}, {__index=function(t,k) local r={pos = Vector(math.huge), ang = Angle()} t[k]=r return r end}) t[k]=r end})
function WireLib.ComputeIfEntityTransformDirty(ent, key, compute)
    local data = ents_computed_data[ent][key]

    local pos, ang = ent:GetPos(), ent:GetAngles()
    if not (rawequal(orientation[1], data.pos) and rawequal(orientation[2], data.ang)) then
        data.pos = pos
        data.ang = ang
        data.computed = compute(ent)
    end
    return data.computed
end

-- Example usage
local wires = WireLib.ComputeIfEntityTransformDirty(ent, "wires", function(ent)
    return {<list of transformed vectors>}
end)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also prevent memory leaking caused by the vector lookup table

Copy link
Contributor

@thegrb93 thegrb93 Jan 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better:

function WireLib.GetComputeIfEntityTransformDirty(compute)
    local ents_computed_data = setmetatable({}, {__index=function(t,k) local r={pos = Vector(math.huge), ang = Angle()} t[k]=r return r end})

    return function(ent, ...)
        local data = ents_computed_data[ent]

        local pos, ang = ent:GetPos(), ent:GetAngles()
        if not (rawequal(orientation[1], data.pos) and rawequal(orientation[2], data.ang)) then
            data.pos = pos
            data.ang = ang
            data.computed = compute(ent, ...)
        end
        return data.computed
    end
end

-- Example usage
local wireTransformer = WireLib.GetComputeIfEntityTransformDirty(function(ent, local_wires)
    return {<list of transformed vectors>}
end)

local wirePositions = wireTransformer(ent, local_wires)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea to put it on the entity itself. Will look into doing it like that this weekend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to do this much longer ago. Gonna actually do it now.

Also, fuck off github-actions bot.

Copy link
Contributor Author

@Fasteroid Fasteroid Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data.computed = compute(ent, ...)

Varargs are not jittable, going to avoid this

Copy link

github-actions bot commented Feb 4, 2024

This pull request has been marked as stale as there haven't been any changes in the past month. It will be closed in 15 days.

@github-actions github-actions bot added the Stale label Feb 4, 2024
@Fasteroid Fasteroid requested a review from thegrb93 February 9, 2024 21:50
@Fasteroid Fasteroid force-pushed the faster-wire-rendering branch from fa8981c to da4aec6 Compare February 9, 2024 22:03
GetPos and GetAngles return new objects every time they are called.
@Fasteroid
Copy link
Contributor Author

@thegrb93 With all due respect, while your code is cleaner, it looks like it might be really really expensive to use in practice. I will not be implementing your requested changes at this time.

@github-actions github-actions bot removed the Stale label Feb 10, 2024
@thegrb93
Copy link
Contributor

thegrb93 commented Feb 15, 2024

Improved the implementation to cache vectors like yours and did more optimization. If its still slow then we can revert to the simpler method, but this should be as fast as if not faster.

https://github.com/wiremod/wire/pull/2955/files/ae6dedf29ed8a7667051d2e8c239ce5f8492b519..6de760bda40e2fbb01909b513b2e4a01038ec775

@thegrb93
Copy link
Contributor

Can be simplified further

function WireLib.GetComputeIfEntityTransformDirty(compute)
	return setmetatable({}, {
		__index=function(t,ent) local r={Vector(math.huge), Angle()} t[ent]=r return r end,
		__call=function(t,ent)
			local data = t[ent]
			local pos, ang = GetPos(ent), GetAngles(ent)
			if pos~=data[1] or ang~=data[2] then
				data[1] = pos
				data[2] = ang
				data[3] = compute(ent)
			end
			return data[3]
		end
	})
end

@thegrb93
Copy link
Contributor

Seems to be working

@thegrb93 thegrb93 merged commit bb63469 into wiremod:master Feb 24, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants