Xonotic
deglobalization.qh
Go to the documentation of this file.
1 #include "lib/accumulate.qh"
2 #include "lib/float.qh"
3 #include "lib/log.qh"
4 #include "lib/misc.qh"
5 #include "lib/static.qh"
6 #include "lib/vector.qh"
7 
8 // These macros wrap functions which use globals so mutation of global state only occurs inside them and is not visible from outside.
9 // This helps prevent bugs where refactoring accidentally breaks implicit assumptions about global state ("pls call makevectors before calling this").
10 // Currently only functions that use v_forward/v_right/v_up are wrapped since those are most common.
11 // Some functions don't have wrappers because they're not used anywhere.
12 
13 // Steps (slightly inspired by steps in self.qh):
14 // 1) (done) Create alternative names for the builtins (e.g. _makevectors_hidden) to be used inside wrappers.
15 // Shadow the originals with macros that tell the user to use the wrappers instead. These are in the *defs.qh files.
16 // 2) Create wrapper macros with the same name (e.g. makevectors) that still use globals but log their usage.
17 // - Would be nice to also log reads and writes to the globals themselves. Probably possible with macros, comma expressions and [[alias]].
18 // 3) Create wrapper macros that use locals (e.g. MAKE_VECTORS).
19 // TODO stuff in the engine that uses the v_forward/v_right/v_up globals and is not wrapped yet:
20 // - RF_USEAXIS, addentities, predraw,
21 // - CL_GetEntityMatrix (in engine but is called from other functions so transitively any of them can use the globals - e.g. V_CalcRefdef, maybe others)
22 // - however RF_USEAXIS is only used if MF_ROTATE is used which is only set in one place
23 // - e.camera_transform / CL_VM_TransformView (in engine)
24 // - this is the only used function that both sets and gets the globals (aim does too but isn't used in our code)
25 // 4) Gradually replace uses of each function with its wrapper.
26 // 5) When a function is no longer used, remove the wrapper with the same name to cause compile errors that will prevent accidental use in the future.
27 
28 // Final checking:
29 // When all functions which use a global have been replaced with the wrappers,
30 // the wrappers can check that the global contains NaN before its use and set it to NaN after its use.
31 // This should detect if there is any remaining global mutation (even in the engine).
32 // NaN is the most likely value to expose remaining usages - e.g. functions like traceline crash on it.
33 
34 #ifdef GAMEQC // menu doesn't use any globals
35 
36 // compile time switches in case perf is an issue
37 #define DEGLOB_LOGGING 1
38 #define DEGLOB_CLEAR 1
39 
40 const int DEGLOB_ORIGINAL = 1;
41 const int DEGLOB_WRAPPED = 2;
42 #if DEGLOB_LOGGING
43 int autocvar_debug_deglobalization_logging = 0;
44 // Varargs to this should already be stringized, otherwise they're expanded first which makes them less readable.
45 // The downside is redundant quotes, fortunately none of these functions take strings.
46 #define DEGLOB_LOG(kind, name, ...) deglob_log(kind, name, __FILE__, __LINE__, __FUNC__, #__VA_ARGS__)
47 // This needs to be a function, not a macro,
48 // because some wrappers of the old functions need to use comma expressions
49 // because they return values.
50 void deglob_log(int kind, string name, string file, int line, string func, string more_text) {
51  if (autocvar_debug_deglobalization_logging & kind) {
52  LOG_INFOF("%s %f %s %s:%d:%s args: %s", PROGNAME, time, name, file, line, func, more_text);
53  }
54 }
55 #else
56 #define DEGLOB_LOG(kind, name, ...)
57 void deglob_log(int kind, string name, string file, int line, string func, string more_text) {}
58 #endif
59 
60 // convenience for deglobalization code - don't use these just to hide that globals are still used
61 #define GET_V_GLOBALS(forward, right, up) MACRO_BEGIN forward = v_forward; right = v_right; up = v_up; MACRO_END
62 #define SET_V_GLOBALS(forward, right, up) MACRO_BEGIN v_forward = forward; v_right = right; v_up = up; MACRO_END
63 #if DEGLOB_CLEAR
64 bool autocvar_debug_deglobalization_clear = true;
65 #define CLEAR_V_GLOBALS() MACRO_BEGIN \
66  if (autocvar_debug_deglobalization_clear) { \
67  v_forward = VEC_NAN; v_right = VEC_NAN; v_up = VEC_NAN \
68  } \
69 MACRO_END
70 STATIC_INIT(globals) {
71  // set to NaN to more easily detect uninitialized use
72  CLEAR_V_GLOBALS();
73 }
74 #else
75 #define CLEAR_V_GLOBALS()
76 #endif
77 
81 #define MAKE_VECTORS(angles, forward, right, up) MACRO_BEGIN \
82  DEGLOB_LOG(DEGLOB_WRAPPED, "MAKE_VECTORS", #angles); \
83  _makevectors_hidden(angles); \
84  GET_V_GLOBALS(forward, right, up); \
85  CLEAR_V_GLOBALS(); \
86 MACRO_END
87 
89 #define SKEL_GET_BONE_ABS(skel, bonenum, forward, right, up, origin) MACRO_BEGIN \
90  DEGLOB_LOG(DEGLOB_WRAPPED, "SKEL_GET_BONE_ABS", #skel, #bonenum); \
91  origin = _skel_get_boneabs_hidden(skel, bonenum) \
92  GET_V_GLOBALS(forward, right, up); \
93  CLEAR_V_GLOBALS(); \
94 MACRO_END
95 
96 #define SKEL_SET_BONE(skel, bonenum, org, forward, right, up) MACRO_BEGIN \
97  DEGLOB_LOG(DEGLOB_WRAPPED, "SKEL_SET_BONE", #skel, #bonenum, #org); \
98  SET_V_GLOBALS(forward, right, up); \
99  _skel_set_bone_hidden(skel, bonenum, org); \
100  CLEAR_V_GLOBALS(); \
101 MACRO_END
102 
103 #define ADD_DYNAMIC_LIGHT(org, radius, lightcolours, forward, right, up) MACRO_BEGIN \
104  DEGLOB_LOG(DEGLOB_WRAPPED, "ADD_DYNAMIC_LIGHT", #org, #radius, #lightcolours); \
105  SET_V_GLOBALS(forward, right, up); \
106  _adddynamiclight_hidden(org, radius, lightcolours); \
107  CLEAR_V_GLOBALS(); \
108 MACRO_END
109 
110 #define VECTOR_VECTORS(forward_in, forward, right, up) MACRO_BEGIN \
111  DEGLOB_LOG(DEGLOB_WRAPPED, "VECTOR_VECTORS", #forward_in); \
112  _vectorvectors_hidden(forward_in); \
113  GET_V_GLOBALS(forward, right, up); \
114  CLEAR_V_GLOBALS(); \
115 MACRO_END
116 
118 #define GET_TAG_INFO(ent, tagindex, forward, right, up, origin) MACRO_BEGIN \
119  DEGLOB_LOG(DEGLOB_WRAPPED, "GET_TAG_INFO", #ent, #tagindex); \
120  origin = _gettaginfo_hidden(ent, tagindex); \
121  GET_V_GLOBALS(forward, right, up); \
122  CLEAR_V_GLOBALS(); \
123 MACRO_END
124 
125 #undef makevectors
126 #define makevectors(angles) MACRO_BEGIN \
127  DEGLOB_LOG(DEGLOB_ORIGINAL, "makevectors", #angles); \
128  _makevectors_hidden(angles); \
129 MACRO_END
130 
131 #undef skel_get_boneabs
132 #define skel_get_boneabs(skel, bonenum) ( \
133  deglob_log(DEGLOB_ORIGINAL, "skel_get_boneabs", __FILE__, __LINE__, __FUNC__, #skel ", " #bonenum), \
134  _skel_get_boneabs_hidden(skel, bonenum) \
135 )
136 
137 #undef skel_set_bone
138 #define skel_set_bone(skel, bonenum, org) MACRO_BEGIN \
139  DEGLOB_LOG(DEGLOB_ORIGINAL, "skel_set_bone", #skel, #bonenum, #org); \
140  _skel_set_bone_hidden(skel, bonenum, org); \
141 MACRO_END
142 
143 #undef adddynamiclight
144 #define adddynamiclight(org, radius, lightcolours) MACRO_BEGIN \
145  DEGLOB_LOG(DEGLOB_ORIGINAL, "adddynamiclight", #org, #radius, #lightcolours); \
146  _adddynamiclight_hidden(org, radius, lightcolours); \
147 MACRO_END
148 
149 #undef gettaginfo
150 #define gettaginfo(ent, tagindex) ( \
151  deglob_log(DEGLOB_ORIGINAL, "gettaginfo", __FILE__, __LINE__, __FUNC__, #ent ", " #tagindex), \
152  _gettaginfo_hidden(ent, tagindex) \
153 )
154 
155 #endif // GAMEQC
#define STATIC_INIT(func)
during worldspawn
Definition: static.qh:32
#define LOG_INFOF(...)
Definition: log.qh:71
float time
Definition: csprogsdefs.qc:16