Files
Bubberstation/code/modules/mapping/space_management/space_transition.dm
T
LemonInTheDark d34fa4c642 Macro optimizes SSmapping saving 50% (#69632)
* 'optimizes' space transitions by like 0.06 seconds, makes them easier to read tho, so that's an upside

* ''''optimizes'''' parsed map loading

I'm honestly not sure how big a difference this makes, looked like small
percentage points if anything
It's a bit more internally concistent at least, which is nice. Also I
understand the system now.

I'd like to think it helped but I think this is kinda a "do you think
it's easier to read" sort of situation. if it did help it was by the
skin of its teeth

* Saves 0.6 seconds off loading meta and lavaland's map files

This is just a lot of micro stuff.
1: Bound checks don't need to be inside for loops, we can instead bound the iteration counts
2: TGM and DMM are parsed differently. in dmm a grid_set is one z level,
   in tgm it's one collumn. Realizing this allows you to skip copytexts and
   other such silly in the tgm implemenentation, saving a good bit of time
3: Min/max bounds do not need to be checked inside for loops, and can
   instead be handled outside of them, because we know the order of x
   and y iteration. This saves 0.2 seconds

I may or may not have made the code harder to read, if so let me know
and I'll check it over.

* Micro ops key caching significantly. Fixes macros bug

inserting \ into a dmm with no valid target would just less then loop
the string. Dumb

Anyway, optimizations. I save a LOT of time by not needing to call
find_next_delimiter_position for every entry and var set. (like maybe 0.5
seconds, not totally sure)
I save this by using splittext, which is significantly faster. this
would cause parsing issues if you could embed \n into dmms, but you
can't, so I'm safe.

Lemme see uh, lots of little things, stuff that's suboptimal or could be
done cheaper. Some "hey you and I both know a \" is 2 chars long sort of
stuff

I removed trim_text because the quote trimming was never actually used,
and the space trimming was slower then using the code in trim. I also
micro'd trim to save a bit of time. this saves another maybe 0.5.

Few other things, I think that's the main of it. Gives me the fuzzy
feelings

* Saves 50% of build_coordinate's time

Micro optimizing go brrrrr
I made turf_blacklist an assoc list rather then just a normal one, so
lookups are O(log n) instead of O(n). Also it's faster for the base case
of loading mostly space.

Instead of toggling the map loader right before and right after New()
calls, we toggle at the start of mapload, and disable then reenable if
we check tick. This saves like 0.3 seconds

Rather then tracking an area cache ourselves, and needing to pass it
around, we use a locally static list to reference the global list of
area -> type. This is much faster, if slightly fragile.

Rather then checking for a null turf at every line, we do it at the
start of the proc and not after. Faster this way, tho it can in theory
drop area vvs.

Avoids calling world.preloader_setup unless we actually have a unique
set of attributes. We use another static list to make this comparison
cheap. This saves another 0.3

Rather then checking for area paths in the turf logic, or vis versa, we
assume we are creating the type implied by the index we're reading off.
So only the last type entry will be loaded like a turf, etc.
This is slightly unsafe but saves a good bit of time, and will properly
error on fucked maps.

Also, rather then using a datum to hold preloader vars, we use 2 global
variables. This is faster.

This marks the end of my optimizations for direct maploading. I've
reduced the cost of loading a map by more then 50% now. Get owned.

* Adds a define for maploading tick check

* makes shuttles load again, removes some of the hard limits I had on the reader for profiling

* Macro ops cave generation

Cave generation was insanely more expensive then it had any right to be.
Maybe 0.5 seconds was saved off not doing a range(12) for EVERY SPAWNED
MOB.
0.14 was saved off using expanded weighted lists (A new idea of mine)
This is useful because I can take a weighted list, and condense it into
weight * path count. This is more memory heavy, and costs more to
create, but is so much faster then the proc.

I also added a naive implementation of gcd to make this a bit less bad.
It's not great, but it'll do for this usecase.

Oh and I changed some ChangeTurfs into New()s. I'm still not entirely
sure what the core difference between the two is, but it seems to work
fine.
I believe it's safe because the turf below us hasn't init'd yet, there's
nothing to take from them. It's like 3 seconds faster too so I'll be sad
when it turns out I'm being dumb

* Micros river spawning

This uses the same sort of concepts as the last change, mostly New being
preferable to ChangeTurf at this level of code.
This bit isn't nearly as detailed as the last few, I honestly got a bit
tired. It's still like 0.4 seconds saved tho

* Micros ruin loading

Turns out it saves time if you don't check area type for every tile on a
ruin. Not a whole ton faster, like 0.03, but faster.

Saves even more time (0.1) to not iterate all your ruin's turfs 3 times
to clear away lavaland mobs, when you're IN SPACE who wrote this.

Oh it also saves time to only pull your turf list once, rather then 3
times
2022-09-22 15:34:10 -07:00

162 lines
6.3 KiB
Plaintext

/datum/space_level/proc/set_linkage(new_linkage)
linkage = new_linkage
if(linkage == SELFLOOPING)
neigbours = list(TEXT_NORTH,TEXT_SOUTH,TEXT_EAST,TEXT_WEST)
for(var/A in neigbours)
neigbours[A] = src
/datum/space_level/proc/set_neigbours(list/L)
for(var/datum/space_transition_point/P in L)
if(P.x == xi)
if(P.y == yi+1)
neigbours[TEXT_NORTH] = P.spl
P.spl.neigbours[TEXT_SOUTH] = src
else if(P.y == yi-1)
neigbours[TEXT_SOUTH] = P.spl
P.spl.neigbours[TEXT_NORTH] = src
else if(P.y == yi)
if(P.x == xi+1)
neigbours[TEXT_EAST] = P.spl
P.spl.neigbours[TEXT_WEST] = src
else if(P.x == xi-1)
neigbours[TEXT_WEST] = P.spl
P.spl.neigbours[TEXT_EAST] = src
#define CHORDS_TO_1D(x, y, grid_diameter) ((x) + ((y) - 1) * (grid_diameter))
/datum/space_transition_point //this is explicitly utilitarian datum type made specially for the space map generation and are absolutely unusable for anything else
var/list/neigbours = list()
var/x
var/y
var/datum/space_level/spl
/datum/space_transition_point/New(nx, ny, list/grid)
if(!grid)
qdel(src)
return
var/grid_diameter = sqrt(length(grid))
if(nx > grid_diameter || ny > grid_diameter)
stack_trace("Attempted to set a position outside the size of [grid_diameter]")
qdel(src)
return
x = nx
y = ny
var/position = CHORDS_TO_1D(x, y, grid_diameter)
if(grid[position])
return
grid[position] = src
/datum/space_transition_point/proc/set_neigbours(list/grid, size)
neigbours.Cut()
if(x+1 <= size)
neigbours |= grid[CHORDS_TO_1D(x+1, y, size)]
if(x-1 >= 1)
neigbours |= grid[CHORDS_TO_1D(x-1, y, size)]
if(y+1 <= size)
neigbours |= grid[CHORDS_TO_1D(x, y + 1, size)]
if(y-1 >= 1)
neigbours |= grid[CHORDS_TO_1D(x, y - 1, size)]
/datum/controller/subsystem/mapping/proc/setup_map_transitions() //listamania
var/list/transition_levels = list()
var/list/cached_z_list = z_list
for(var/datum/space_level/level as anything in cached_z_list)
if (level.linkage == CROSSLINKED)
transition_levels.Add(level)
var/grid_diameter = (length(transition_levels) * 2) + 1
var/list/grid = new /list(grid_diameter ** 2)
var/datum/space_transition_point/point
for(var/x in 1 to grid_diameter)
for(var/y in 1 to grid_diameter)
point = new/datum/space_transition_point(x, y, grid)
grid[CHORDS_TO_1D(x, y, grid_diameter)] = point
for(point as anything in grid)
point.set_neigbours(grid, grid_diameter)
var/center = round(grid_diameter / 2)
point = grid[CHORDS_TO_1D(grid_diameter, center, center)]
grid.Cut()
var/list/transition_pick = transition_levels.Copy()
var/list/possible_points = list()
var/list/used_points = list()
while(transition_pick.len)
var/datum/space_level/level = pick_n_take(transition_pick)
level.xi = point.x
level.yi = point.y
point.spl = level
possible_points |= point.neigbours
used_points |= point
possible_points.Remove(used_points)
level.set_neigbours(used_points)
point = pick(possible_points)
CHECK_TICK
// Now that we've handed out neighbors, we're gonna handle an edge case
// Need to check if all our levels have neighbors in all directions
// If they don't, we'll make them wrap all the way around to the other side of the grid
for(var/direction in GLOB.cardinals)
var/dir = "[direction]"
var/inverse = "[turn(direction, 180)]"
for(var/datum/space_level/level as anything in transition_levels)
// If we have something in this dir that isn't just us, continue on
if(level.neigbours[dir] && level.neigbours[dir] != level)
continue
var/datum/space_level/head = level
while(head.neigbours[inverse] && head.neigbours[inverse] != head)
head = head.neigbours[inverse]
// Alllright we've landed on someone who we can wrap around onto safely, let's make that connection yeah?
head.neigbours[inverse] = level
level.neigbours[dir] = head
//Lists below are pre-calculated values arranged in the list in such a way to be easily accessable in the loop by the counter
//Its either this or madness with lotsa math
var/inner_max_x = world.maxx - TRANSITIONEDGE
var/inner_max_y = world.maxy - TRANSITIONEDGE
var/list/x_pos_beginning = list(1, 1, inner_max_x, 1) //x values of the lowest-leftest turfs of the respective 4 blocks on each side of zlevel
var/list/y_pos_beginning = list(inner_max_y, 1, 1 + TRANSITIONEDGE, 1 + TRANSITIONEDGE) //y values respectively
var/list/x_pos_ending = list(world.maxx, world.maxx, world.maxx, 1 + TRANSITIONEDGE) //x values of the highest-rightest turfs of the respective 4 blocks on each side of zlevel
var/list/y_pos_ending = list(world.maxy, 1 + TRANSITIONEDGE, inner_max_y, inner_max_y) //y values respectively
var/list/x_pos_transition = list(1, 1, TRANSITIONEDGE + 2, inner_max_x - 1) //values of x for the transition from respective blocks on the side of zlevel, 1 is being translated into turfs respective x value later in the code
var/list/y_pos_transition = list(TRANSITIONEDGE + 2, inner_max_y - 1, 1, 1) //values of y for the transition from respective blocks on the side of zlevel, 1 is being translated into turfs respective y value later in the code
for(var/datum/space_level/level as anything in cached_z_list)
if(!level.neigbours.len)
continue
var/zlevelnumber = level.z_value
for(var/side in 1 to 4)
var/turf/beginning = locate(x_pos_beginning[side], y_pos_beginning[side], zlevelnumber)
var/turf/ending = locate(x_pos_ending[side], y_pos_ending[side], zlevelnumber)
var/list/turfblock = block(beginning, ending)
var/dirside = 2**(side-1)
var/x_target = x_pos_transition[side] == 1 ? 0 : x_pos_transition[side]
var/y_target = y_pos_transition[side] == 1 ? 0 : y_pos_transition[side]
var/datum/space_level/neighbor = level.neigbours["[dirside]"]
var/zdestination = neighbor.z_value
for(var/turf/open/space/S in turfblock)
S.destination_x = x_target || S.x
S.destination_y = y_target || S.y
S.destination_z = zdestination
// Mirage border code
var/mirage_dir
if(S.x == 1 + TRANSITIONEDGE)
mirage_dir |= WEST
else if(S.x == inner_max_x)
mirage_dir |= EAST
if(S.y == 1 + TRANSITIONEDGE)
mirage_dir |= SOUTH
else if(S.y == inner_max_y)
mirage_dir |= NORTH
if(!mirage_dir)
continue
var/turf/place = locate(S.destination_x, S.destination_y, zdestination)
S.AddComponent(/datum/component/mirage_border, place, mirage_dir)
#undef CHORDS_TO_1D