Skip to content

Commit f34ad10

Browse files
authored
Parallax optimizations (#1091)
* parallax port broken * fixed
1 parent 9f29f3e commit f34ad10

File tree

5 files changed

+119
-83
lines changed

5 files changed

+119
-83
lines changed

code/_compile_options.dm

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
/////////////////////// MISC PERFORMANCE
163163

164164
//uncomment this to load centcom and runtime station and thats it.
165-
// #define LOWMEMORYMODE
165+
#define LOWMEMORYMODE
166166

167167
//uncomment to enable the spatial grid debug proc.
168168
// #define SPATIAL_GRID_ZLEVEL_STATS

code/_onclick/hud/parallax.dm

+86-74
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
if (!apply_parallax_pref(viewmob)) //don't want shit computers to crash when specing someone with insane parallax, so use the viewer's pref
88
return
99

10+
if(isnull(C.parallax_master))
11+
C.parallax_master = new(null, src)
12+
13+
C.screen |= C.parallax_master
14+
1015
if(!length(C.parallax_layers_cached))
1116
C.parallax_layers_cached = list()
1217
C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_1(null, src)
@@ -17,11 +22,13 @@
1722
if (length(C.parallax_layers) > C.parallax_layers_max)
1823
C.parallax_layers.len = C.parallax_layers_max
1924

20-
C.screen |= (C.parallax_layers)
25+
C.parallax_master.vis_contents = C.parallax_layers
26+
2127
var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
2228
if(screenmob != mymob)
2329
C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
2430
C.screen += PM
31+
2532
PM.color = list(
2633
0, 0, 0, 0,
2734
0, 0, 0, 0,
@@ -34,11 +41,13 @@
3441
/datum/hud/proc/remove_parallax(mob/viewmob)
3542
var/mob/screenmob = viewmob || mymob
3643
var/client/C = screenmob.client
37-
C.screen -= (C.parallax_layers_cached)
44+
C.screen -= C.parallax_master
45+
3846
var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
3947
if(screenmob != mymob)
4048
C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
4149
C.screen += PM
50+
4251
PM.color = initial(PM.color)
4352
C.parallax_layers = null
4453

@@ -92,74 +101,62 @@
92101
var/client/C = screenmob.client
93102
if(new_parallax_movedir == C.parallax_movedir)
94103
return
95-
var/animatedir = new_parallax_movedir
96-
if(new_parallax_movedir == FALSE)
97-
var/animate_time = 0
98-
for(var/atom/movable/screen/parallax_layer/L as anything in C.parallax_layers)
99-
L.icon_state = initial(L.icon_state)
100-
L.update_o(C.view)
101-
var/T = PARALLAX_LOOP_TIME / L.speed
102-
if (T > animate_time)
103-
animate_time = T
104-
C.dont_animate_parallax = world.time + min(animate_time, PARALLAX_LOOP_TIME)
105-
animatedir = C.parallax_movedir
106-
107-
var/matrix/newtransform
108-
switch(animatedir)
104+
105+
var/animation_dir = new_parallax_movedir || C.parallax_movedir
106+
var/matrix/new_transform
107+
switch(animation_dir)
109108
if(NORTH)
110-
newtransform = matrix(1, 0, 0, 0, 1, PARALLAX_ICON_SIZE)
109+
new_transform = matrix(1, 0, 0, 0, 1, PARALLAX_ICON_SIZE)
111110
if(SOUTH)
112-
newtransform = matrix(1, 0, 0, 0, 1,-PARALLAX_ICON_SIZE)
111+
new_transform = matrix(1, 0, 0, 0, 1,-PARALLAX_ICON_SIZE)
113112
if(EAST)
114-
newtransform = matrix(1, 0, PARALLAX_ICON_SIZE, 0, 1, 0)
113+
new_transform = matrix(1, 0, PARALLAX_ICON_SIZE, 0, 1, 0)
115114
if(WEST)
116-
newtransform = matrix(1, 0,-PARALLAX_ICON_SIZE, 0, 1, 0)
117-
118-
var/shortesttimer
119-
if(!skip_windups)
120-
for(var/atom/movable/screen/parallax_layer/L as anything in C.parallax_layers)
121-
var/T = PARALLAX_LOOP_TIME / L.speed
122-
if (isnull(shortesttimer))
123-
shortesttimer = T
124-
if (T < shortesttimer)
125-
shortesttimer = T
126-
L.transform = newtransform
127-
animate(L, transform = matrix(), time = T, easing = QUAD_EASING | (new_parallax_movedir ? EASE_IN : EASE_OUT), flags = ANIMATION_END_NOW)
128-
if (new_parallax_movedir)
129-
L.transform = newtransform
130-
animate(transform = matrix(), time = T) //queue up another animate so lag doesn't create a shutter
115+
new_transform = matrix(1, 0,-PARALLAX_ICON_SIZE, 0, 1, 0)
131116

132-
C.parallax_movedir = new_parallax_movedir
133-
if (C.parallax_animate_timer)
134-
deltimer(C.parallax_animate_timer)
135-
var/datum/callback/CB = CALLBACK(src, PROC_REF(update_parallax_motionblur), C, animatedir, new_parallax_movedir, newtransform)
136-
if(skip_windups)
137-
CB.Invoke()
138-
else
139-
C.parallax_animate_timer = addtimer(CB, min(shortesttimer, PARALLAX_LOOP_TIME), TIMER_CLIENT_TIME|TIMER_STOPPABLE)
117+
var/longest_timer = 0
118+
for(var/key in C.parallax_animate_timers)
119+
deltimer(C.parallax_animate_timers[key])
120+
C.parallax_animate_timers = list()
140121

122+
for(var/atom/movable/screen/parallax_layer/layer as anything in C.parallax_layers)
123+
var/scaled_time = PARALLAX_LOOP_TIME / layer.speed
124+
if(new_parallax_movedir == NONE) // If we're stopping, we need to stop on the same dime, yeah?
125+
scaled_time = PARALLAX_LOOP_TIME
126+
longest_timer = max(longest_timer, scaled_time)
141127

142-
/datum/hud/proc/update_parallax_motionblur(client/C, animatedir, new_parallax_movedir, matrix/newtransform)
143-
if(!C)
144-
return
145-
C.parallax_animate_timer = FALSE
146-
for(var/thing in C.parallax_layers)
147-
var/atom/movable/screen/parallax_layer/L = thing
148-
if (!new_parallax_movedir)
149-
animate(L)
128+
if(skip_windups)
129+
update_parallax_motionblur(C, layer, new_parallax_movedir, new_transform)
150130
continue
151131

152-
var/newstate = initial(L.icon_state)
153-
var/T = PARALLAX_LOOP_TIME / L.speed
132+
layer.transform = new_transform
133+
animate(layer, transform = matrix(), time = scaled_time, easing = QUAD_EASING | (new_parallax_movedir ? EASE_IN : EASE_OUT))
134+
if (new_parallax_movedir == NONE)
135+
continue
154136

155-
if (newstate in icon_states(L.icon))
156-
L.icon_state = newstate
157-
L.update_o(C.view)
137+
//queue up another animate so lag doesn't create a shutter
138+
animate(transform = new_transform, time = 0)
139+
animate(transform = matrix(), time = scaled_time / 2)
140+
C.parallax_animate_timers[layer] = addtimer(CALLBACK(src, PROC_REF(update_parallax_motionblur), C, layer, new_parallax_movedir, new_transform), scaled_time, TIMER_CLIENT_TIME|TIMER_STOPPABLE)
158141

159-
L.transform = newtransform
142+
C.dont_animate_parallax = world.time + min(longest_timer, PARALLAX_LOOP_TIME)
143+
C.parallax_movedir = new_parallax_movedir
144+
145+
/datum/hud/proc/update_parallax_motionblur(client/C, atom/movable/screen/parallax_layer/layer, new_parallax_movedir, matrix/new_transform)
146+
if(!C)
147+
return
160148

161-
animate(L, transform = L.transform, time = 0, loop = -1, flags = ANIMATION_END_NOW)
162-
animate(transform = matrix(), time = T)
149+
C.parallax_animate_timers -= layer
150+
151+
// If we are moving in a direction, we used the QUAD_EASING function with EASE_IN
152+
// This means our position function is x^2. This is always LESS then the linear we're using here
153+
// But if we just used the same time delay, our rate of change would mismatch. f'(1) = 2x for quad easing, rather then the 1 we get for linear
154+
// (This is because of how derivatives work right?)
155+
// Because of this, while our actual rate of change from before was PARALLAX_LOOP_TIME, our perceived rate of change was PARALLAX_LOOP_TIME / 2 (lower == faster).
156+
// Let's account for that here
157+
var/scaled_time = (PARALLAX_LOOP_TIME / layer.speed) / 2
158+
animate(layer, transform = new_transform, time = 0, loop = -1, flags = ANIMATION_END_NOW)
159+
animate(transform = matrix(), time = scaled_time)
163160

164161
/datum/hud/proc/update_parallax(mob/viewmob)
165162
var/mob/screenmob = viewmob || mymob
@@ -196,36 +193,39 @@
196193
var/our_speed = parallax_layer.speed
197194
var/change_x
198195
var/change_y
196+
var/old_x = parallax_layer.offset_x
197+
var/old_y = parallax_layer.offset_y
198+
199199
if(parallax_layer.absolute)
200200
// We use change here so the typically large absolute objects don't jitter so much
201-
change_x = (posobj.x - SSparallax.planet_x_offset) * our_speed + parallax_layer.offset_x
202-
change_y = (posobj.y - SSparallax.planet_y_offset) * our_speed + parallax_layer.offset_y
201+
change_x = (posobj.x - SSparallax.planet_x_offset) * our_speed + old_x
202+
change_y = (posobj.y - SSparallax.planet_y_offset) * our_speed + old_y
203203
else
204204
change_x = offset_x * our_speed
205205
change_y = offset_y * our_speed
206206

207207
// This is how we tile parralax sprites
208208
// It doesn't use change because we really don't want to animate this
209-
if(parallax_layer.offset_x - change_x > PARALLAX_ICON_SIZE/2)
209+
if(old_x - change_x > PARALLAX_ICON_SIZE/2)
210210
parallax_layer.offset_x -= PARALLAX_ICON_SIZE
211-
else if(parallax_layer.offset_x - change_x < -(PARALLAX_ICON_SIZE/2))
211+
else if(old_x - change_x < -(PARALLAX_ICON_SIZE/2))
212212
parallax_layer.offset_x += PARALLAX_ICON_SIZE
213-
if(parallax_layer.offset_y - change_y > PARALLAX_ICON_SIZE/2)
213+
if(old_y - change_y > PARALLAX_ICON_SIZE/2)
214214
parallax_layer.offset_y -= PARALLAX_ICON_SIZE
215-
else if(parallax_layer.offset_y - change_y < -(PARALLAX_ICON_SIZE/2))
215+
else if(old_y - change_y < -(PARALLAX_ICON_SIZE/2))
216216
parallax_layer.offset_y += PARALLAX_ICON_SIZE
217217

218-
// Now that we have our offsets, let's do our positioning
219218
parallax_layer.offset_x -= change_x
220219
parallax_layer.offset_y -= change_y
221220

222-
parallax_layer.screen_loc = "CENTER-7:[round(parallax_layer.offset_x, 1)],CENTER-7:[round(parallax_layer.offset_y, 1)]"
223-
224-
// We're going to use a transform to "glide" that last movement out, so it looks nicer
221+
// Now that we have our offsets, let's do our positioning
222+
// We're going to use an animate to "glide" that last movement out, so it looks nicer
225223
// Don't do any animates if we're not actually moving enough distance yeah? thanks lad
226224
if(run_parralax && (largest_change * our_speed > 1))
227-
parallax_layer.transform = matrix(1,0,change_x, 0,1,change_y)
228-
animate(parallax_layer, transform=matrix(), time = glide_rate)
225+
animate(parallax_layer, pixel_x = round(parallax_layer.offset_x, 1), pixel_y = round(parallax_layer.offset_y, 1), time = glide_rate)
226+
else
227+
parallax_layer.pixel_x = round(parallax_layer.offset_x, 1)
228+
parallax_layer.pixel_y = round(parallax_layer.offset_y, 1)
229229

230230
/atom/movable/proc/update_parallax_contents()
231231
for(var/mob/client_mob as anything in client_mobs_in_contents)
@@ -237,6 +237,15 @@
237237
var/area/areaobj = get_area(client.eye)
238238
hud_used.set_parallax_movedir(areaobj.parallax_movedir, TRUE)
239239

240+
// Root object for parallax, all parallax layers are drawn onto this
241+
INITIALIZE_IMMEDIATE(/atom/movable/screen/parallax_root)
242+
/atom/movable/screen/parallax_root
243+
icon = null
244+
blend_mode = BLEND_ADD
245+
plane = PLANE_SPACE_PARALLAX
246+
screen_loc = "CENTER-7,CENTER-7"
247+
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
248+
240249
// We need parallax to always pass its args down into initialize, so we immediate init it
241250
INITIALIZE_IMMEDIATE(/atom/movable/screen/parallax_layer)
242251
/atom/movable/screen/parallax_layer
@@ -246,17 +255,16 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/parallax_layer)
246255
var/offset_y = 0
247256
var/view_sized
248257
var/absolute = FALSE
249-
appearance_flags = APPEARANCE_UI | TILE_BOUND
258+
appearance_flags = APPEARANCE_UI | TILE_BOUND | KEEP_TOGETHER
250259
blend_mode = BLEND_ADD
251260
plane = PLANE_SPACE_PARALLAX
252-
screen_loc = "CENTER-7,CENTER-7"
253261
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
254262

255263
/atom/movable/screen/parallax_layer/Initialize(mapload, datum/hud/hud_owner)
256264
. = ..()
257265
// Parallax layers are independant of hud, they care about client
258266
// Not doing this will just create a bunch of hard deletes
259-
hud = null
267+
set_new_hud(null)
260268

261269
var/client/boss = hud_owner?.mymob?.canon_client
262270

@@ -280,16 +288,20 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/parallax_layer)
280288

281289
// Turn the view size into a grid of correctly scaled overlays
282290
var/list/viewscales = getviewsize(view)
291+
// This could be half the size but we need to provide space for parallax movement on mob movement, and movement on scroll from shuttles, so like this instead
283292
var/countx = CEILING((viewscales[1] / 2) * parallax_scaler, 1) + 1
284293
var/county = CEILING((viewscales[2] / 2) * parallax_scaler, 1) + 1
285294
var/list/new_overlays = new
286295
for(var/x in -countx to countx)
287296
for(var/y in -county to county)
288297
if(x == 0 && y == 0)
289298
continue
299+
290300
var/mutable_appearance/texture_overlay = mutable_appearance(icon, icon_state)
291-
texture_overlay.transform = matrix(1, 0, x*PARALLAX_ICON_SIZE, 0, 1, y*PARALLAX_ICON_SIZE)
301+
texture_overlay.pixel_x += PARALLAX_ICON_SIZE * x
302+
texture_overlay.pixel_y += PARALLAX_ICON_SIZE * y
292303
new_overlays += texture_overlay
304+
293305
cut_overlays()
294306
add_overlay(new_overlays)
295307
view_sized = view

code/_onclick/hud/screen_objects.dm

+25-6
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737

3838
/atom/movable/screen/Initialize(mapload, datum/hud/hud_owner)
3939
. = ..()
40-
if(istype(hud_owner))
41-
hud = hud_owner
40+
if(isnull(hud_owner)) //some screens set their hud owners on /new, this prevents overriding them with null post atoms init
41+
return
42+
43+
set_new_hud(hud_owner)
4244

4345
/atom/movable/screen/Destroy()
4446
master_ref = null
@@ -54,20 +56,37 @@
5456

5557
SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr)
5658

57-
/atom/movable/screen/proc/can_usr_use(mob/user)
58-
. = TRUE
59-
if(private_screen && (hud?.mymob != user))
60-
return FALSE
6159

6260
/atom/movable/screen/examine(mob/user)
6361
return list()
6462

6563
/atom/movable/screen/orbit()
6664
return
6765

66+
/atom/movable/screen/proc/can_usr_use(mob/user)
67+
. = TRUE
68+
if(private_screen && (hud?.mymob != user))
69+
return FALSE
70+
71+
///setter used to set our new hud
72+
/atom/movable/screen/proc/set_new_hud(datum/hud/hud_owner)
73+
if(hud)
74+
UnregisterSignal(hud, COMSIG_PARENT_QDELETING)
75+
76+
if(isnull(hud_owner))
77+
hud = null
78+
return
79+
80+
hud = hud_owner
81+
RegisterSignal(hud, COMSIG_PARENT_QDELETING, PROC_REF(on_hud_delete))
82+
6883
/atom/movable/screen/proc/component_click(atom/movable/screen/component_button/component, params)
6984
return
7085

86+
/atom/movable/screen/proc/on_hud_delete(datum/source)
87+
SIGNAL_HANDLER
88+
set_new_hud(hud_owner = null)
89+
7190
/atom/movable/screen/text
7291
icon = null
7392
icon_state = null

code/modules/client/client_defines.dm

+4-2
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@
204204

205205
var/list/parallax_layers
206206
var/list/parallax_layers_cached
207+
var/atom/movable/screen/parallax_root/parallax_master
208+
207209
///this is the last recorded client eye by SSparallax/fire()
208210
var/atom/movable/movingmob
209211
var/turf/previous_turf
@@ -213,8 +215,8 @@
213215
var/parallax_movedir = 0
214216
/// How many parallax layers to show our client
215217
var/parallax_layers_max = 4
216-
/// Timer for the area directional animation
217-
var/parallax_animate_timer
218+
/// Timers for the area directional animation, one for each layer
219+
var/list/parallax_animate_timers
218220
/// Do we want to do parallax animations at all?
219221
/// Exists to prevent laptop fires
220222
var/do_parallax_animations = TRUE

code/modules/client/client_procs.dm

+3
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
600600
QDEL_NULL(view_size)
601601
QDEL_NULL(void)
602602
QDEL_NULL(tooltips)
603+
QDEL_NULL(parallax_master)
604+
QDEL_LIST(parallax_layers_cached)
605+
parallax_layers = null
603606
seen_messages = null
604607
Master.UpdateTickRate()
605608
..() //Even though we're going to be hard deleted there are still some things that want to know the destroy is happening

0 commit comments

Comments
 (0)