]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/lib/deglobalization.qh
Purge client/defs.qh
[xonotic/xonotic-data.pk3dir.git] / qcsrc / lib / deglobalization.qh
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
78 /// Same as the `makevectors` builtin but uses the provided locals instead of the `v_*` globals.
79 /// Always use this instead of raw `makevectors` to make the data flow clear.
80 /// Note that you might prefer `FIXED_MAKE_VECTORS` for new code.
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
88 /// Returns all 4 vectors by assigning to them (instead of returning a value) for consistency (and sanity)
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
117 /// Note that this only avoids the v_* globals, not the gettaginfo_* ones
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