|
| 1 | +# Summary |
| 2 | + |
| 3 | +- Apply the MVC pattern |
| 4 | +- Global config, profile and scene collections are represented by their own object |
| 5 | +- Frontend API functions related to config file are replaced |
| 6 | + |
| 7 | +# Motivation |
| 8 | + |
| 9 | +The actual code is almost a chimera of software architectural patterns, applying the MVC pattern will allow to untangle the code and make it more clear. |
| 10 | + |
| 11 | +# Design |
| 12 | + |
| 13 | +**The naming of new types is not final.** |
| 14 | + |
| 15 | +Flowchart diagrams will be used as illustration so the following is the key for those: |
| 16 | +- Parallelogram nodes represent concepts/abstraction. |
| 17 | +- Rectangle nodes represent a piece of software (code, classes, API…). |
| 18 | + - If with round edges, a user can see it and/or interract with it. |
| 19 | +- Hexagon nodes represent plugins |
| 20 | + |
| 21 | +This is a diagram representing basic MVC: |
| 22 | +```mermaid |
| 23 | +flowchart LR |
| 24 | + user(["User(s)"]) |
| 25 | + model[/OBS Model/] |
| 26 | + view[/OBS View/] |
| 27 | + controller[/OBS Controller/] |
| 28 | +
|
| 29 | + user-- "interact with" -->view |
| 30 | + view-- "user action" -->controller |
| 31 | + controller-- update -->view |
| 32 | + controller-- manipulate -->model |
| 33 | + model-- notify -->controller |
| 34 | +``` |
| 35 | +--- |
| 36 | + |
| 37 | +The following is the same diagram but considering splitting the model in three like it is in OBS Studio (global, profile, scene collection): |
| 38 | + ```mermaid |
| 39 | +flowchart LR |
| 40 | + user(["User(s)"]) |
| 41 | + view[/OBS View/] |
| 42 | + controller[/OBS Controller/] |
| 43 | +
|
| 44 | + globconf[/Global Config/] |
| 45 | + profile[/Profile/] |
| 46 | + scenecol[/Scene Collection/] |
| 47 | +
|
| 48 | + user-- "interact with" -->view |
| 49 | + view-- "user action" -->controller |
| 50 | + controller-- "update" -->view |
| 51 | +
|
| 52 | + controller-- manipulate -->globconf |
| 53 | + controller-- manipulate -->profile |
| 54 | + controller-- manipulate -->scenecol |
| 55 | + scenecol-- notify -->controller |
| 56 | + |
| 57 | +``` |
| 58 | +--- |
| 59 | + |
| 60 | +If those concept are replaced by their matching elements in the actual OBS Studio (without considering the frontend API), it looks like that: |
| 61 | +```mermaid |
| 62 | +flowchart LR |
| 63 | + user(["User(s)"]) |
| 64 | + ui("OBS Studio UI") |
| 65 | +
|
| 66 | + globconf["globalConfig (ConfigFile)"] |
| 67 | +
|
| 68 | + subgraph profile [Profile] |
| 69 | + basicconf["basicConfig (ConfigFile)"] |
| 70 | + streamenc["streamEncoder (OBSData)"] |
| 71 | + recordenc["recordEncoder (OBSData)"] |
| 72 | + service["service (OBSService)"] |
| 73 | + end |
| 74 | +
|
| 75 | + user-- "interact with" -->ui |
| 76 | + ui-- "user action" -->ui |
| 77 | + ui-- "update view" -->ui |
| 78 | +
|
| 79 | + ui-- manipulate -->globconf |
| 80 | + ui-- manipulate --> basicconf |
| 81 | + ui-- manipulate --> streamenc |
| 82 | + ui-- manipulate --> recordenc |
| 83 | + ui-- manipulate --> service |
| 84 | + ui-- "manipulate scene collection" -->ui |
| 85 | + ui-- "scene collection notify" -->ui |
| 86 | +``` |
| 87 | + |
| 88 | +"*OBS Studio UI*" combines "*OBS View*", "*OBS Controller*" and "*Scene Collection*" which is a part of the "*OBS Model*". |
| 89 | +Note that ConfigFile is not a good type to represent *globalConfig* and *basicConfig*. |
| 90 | + |
| 91 | +"*Profile*" is also splitted into three or four parts (*recordEncoder* is unused if the encode is the same as the stream), so this one really need a type to represent it. |
| 92 | + |
| 93 | +Note: from now, the node "*Profile*" will be used if the diagram is not related to "*Profile*" itself. |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +So by applying MVC, "*OBS Studio UI*" should only serve as "*OBS View*": |
| 98 | +```mermaid |
| 99 | +flowchart LR |
| 100 | + user(["User(s)"]) |
| 101 | +
|
| 102 | + subgraph view ["OBS View"] |
| 103 | + ui("OBS Studio UI") |
| 104 | + end |
| 105 | +
|
| 106 | + controller[/OBS Controller/] |
| 107 | +
|
| 108 | + globconf["globalConfig (ConfigFile)"] |
| 109 | + profile[/Profile/] |
| 110 | + scenecol[/Scene Collection/] |
| 111 | +
|
| 112 | + user-- "interact with" -->ui |
| 113 | + ui-- "user action" -->controller |
| 114 | + controller-- update -->ui |
| 115 | + controller-- manipulate -->globconf |
| 116 | + controller-- manipulate -->profile |
| 117 | + controller-- manipulate -->scenecol |
| 118 | + scenecol-- notify -->controller |
| 119 | +``` |
| 120 | + |
| 121 | +## "*Scene Collection*" and "*OBS Studio UI*" |
| 122 | + |
| 123 | +The "*Scene Collection*" contains everything related to scenes, sources (and filters) are tightly connected to them: |
| 124 | +- Transitions |
| 125 | +- Projectors |
| 126 | +- Scaling of the preview |
| 127 | +- Video mix configuration for the virtual camera |
| 128 | +- Settings of some plugins bound to the collection |
| 129 | + |
| 130 | +Except the last point, everything is stored in parts of the UI. "*Scene Collection*" shall be split from it into a new type/object. |
| 131 | + |
| 132 | +```mermaid |
| 133 | +flowchart LR |
| 134 | + user(["User(s)"]) |
| 135 | +
|
| 136 | + ui("OBS Studio UI") |
| 137 | +
|
| 138 | + globconf["globalConfig (ConfigFile)"] |
| 139 | + profile[/Profile/] |
| 140 | + scenecol["sceneCollection (OBSSceneCollection)"] |
| 141 | +
|
| 142 | + user-- "interact with" -->ui |
| 143 | + ui-- "user action" -->ui |
| 144 | + ui-- "update view" -->ui |
| 145 | +
|
| 146 | + ui-- manipulate -->globconf |
| 147 | + ui-- manipulate --> profile |
| 148 | + ui-- manipulate --> scenecol |
| 149 | + scenecol-- notify -->ui |
| 150 | +``` |
| 151 | + |
| 152 | +The changes around the "*notify*" part are explained later. |
| 153 | + |
| 154 | +## "*Profile*" and "*Global Config*" |
| 155 | + |
| 156 | +New types/objects will be created to represent those: |
| 157 | + |
| 158 | +```mermaid |
| 159 | +flowchart LR |
| 160 | + user(["User(s)"]) |
| 161 | +
|
| 162 | + ui("OBS Studio UI") |
| 163 | +
|
| 164 | + globconf["globalConfig (OBSGlobalConfig)"] |
| 165 | + profile["profile (OBSProfile)"] |
| 166 | + scenecol["sceneCollection (OBSSceneCollection)"] |
| 167 | +
|
| 168 | + user-- "interact with" -->ui |
| 169 | + ui-- "user action" -->ui |
| 170 | + ui-- "update view" -->ui |
| 171 | +
|
| 172 | + ui-- manipulate -->globconf |
| 173 | + ui-- manipulate --> profile |
| 174 | + ui-- manipulate --> scenecol |
| 175 | + scenecol-- notify -->ui |
| 176 | +``` |
| 177 | + |
| 178 | +Note: *OBSProfile* also includes encoders and service. |
| 179 | + |
| 180 | +Their ConfigFile could be openned only when loading and saving but… |
| 181 | + |
| 182 | +### "*OBS Frontend API*" and ConfigFile |
| 183 | + |
| 184 | +"*OBS Frontend API*" provide functions to access *globalConfig* and *basicConfig* (profile) and modify them without restriction ignoring "*OBS Controller*", the program is not even aware is one of its own config happen to be changed. |
| 185 | + |
| 186 | +```mermaid |
| 187 | +flowchart LR |
| 188 | + plugins{{"Plugin(s)"}} |
| 189 | +
|
| 190 | + ui("OBS Studio UI") |
| 191 | + api["OBS Frontend API"] |
| 192 | +
|
| 193 | +
|
| 194 | + subgraph model [OBS Model] |
| 195 | + globconf["globalConfig (ConfigFile)"] |
| 196 | + subgraph profile [Profile] |
| 197 | + basicconf["basicConfig (ConfigFile)"] |
| 198 | + streamenc["streamEncoder (OBSData)"] |
| 199 | + recordenc["recordEncoder (OBSData)"] |
| 200 | + service["service (OBSService)"] |
| 201 | + end |
| 202 | + scenecol["sceneCollection (OBSSceneCollection)"] |
| 203 | + end |
| 204 | +
|
| 205 | + plugins-- action -->api |
| 206 | + api-- "event callback" -->plugins |
| 207 | +
|
| 208 | + ui-- "frontend events" -->api |
| 209 | + api-- action -->ui |
| 210 | +
|
| 211 | + ui-- "update view" -->ui |
| 212 | +
|
| 213 | + ui-- manipulate -->globconf |
| 214 | + ui-- manipulate --> basicconf |
| 215 | + ui-- manipulate --> streamenc |
| 216 | + ui-- manipulate --> recordenc |
| 217 | + ui-- manipulate --> service |
| 218 | + ui-- manipulate --> scenecol |
| 219 | +
|
| 220 | + plugins-- "manipulate configs" -->api |
| 221 | + api-- manipulate -->globconf |
| 222 | + api-- manipulate --> basicconf |
| 223 | +``` |
| 224 | + |
| 225 | +So `obs_frontend_get_global_config` and `obs_frontend_get_profile_config` will be deprecated. |
| 226 | + |
| 227 | +Until those are removed, OBSGlobalConfig or OBSProfile will open the ConfigFile and keep it updated. But changing a OBS config from a plugin will have no effect, the stored value will overwrite what the plugin has put. |
| 228 | + |
| 229 | +To replace those two functions to allow plugins to save their config in the global config or the profile. Functions like the following will be created for `bool`, `double`, `int64_t`, `uint64_t` and `const char *`(string), the config section will be named after the plugin filename: |
| 230 | + |
| 231 | +```c |
| 232 | +#define obs_frontend_set_global_bool_config(name, value) \ |
| 233 | + obs_frontend_set_global_module_bool_config(obs_current_module(), name, value) |
| 234 | +EXPORT void obs_frontend_set_global_module_bool_config(obs_module_t *module, const char *name, bool value); |
| 235 | + |
| 236 | +#define obs_frontend_get_global_bool_config(name) \ |
| 237 | + obs_frontend_get_global_module_bool_config(obs_current_module(), name) |
| 238 | +EXPORT bool obs_frontend_get_global_module_bool_config(obs_module_t *module, const char *name); |
| 239 | +``` |
| 240 | +
|
| 241 | +```c |
| 242 | +#define obs_frontend_set_profile_bool_config(name, value) \ |
| 243 | + obs_frontend_set_profile_module_bool_config(obs_current_module(), name, value) |
| 244 | +EXPORT void obs_frontend_set_profile_module_bool_config(obs_module_t *module, const char *name, bool value); |
| 245 | +
|
| 246 | +#define obs_frontend_get_profile_bool_config(name) \ |
| 247 | + obs_frontend_get_profile_module_bool_config(obs_current_module(), name) |
| 248 | +EXPORT bool obs_frontend_get_profile_module_bool_config(obs_module_t *module, const char *name); |
| 249 | +``` |
| 250 | + |
| 251 | +Those settings will be stored in OBSGlobalConfig or OBSProfile, and then written in the file while saving. |
| 252 | + |
| 253 | +To check if functions to get some OBS config is needed, asking third-party plugins developper may be needed. |
| 254 | + |
| 255 | +Now "*OBS Frontend API*" no longer bypass "*OBS Controller*" to manipulate the global config and the profile: |
| 256 | + |
| 257 | +```mermaid |
| 258 | +flowchart LR |
| 259 | + plugins{{"Plugin(s)"}} |
| 260 | + |
| 261 | + ui("OBS Studio UI") |
| 262 | + api["OBS Frontend API"] |
| 263 | + |
| 264 | + globconf["globalConfig (OBSGlobalConfig)"] |
| 265 | + profile["profile (OBSProfile)"] |
| 266 | + scenecol["sceneCollection (OBSSceneCollection)"] |
| 267 | + |
| 268 | + plugins-- action -->api |
| 269 | + api-- "event callback" -->plugins |
| 270 | + |
| 271 | + ui-- "frontend events" -->api |
| 272 | + api-- action -->ui |
| 273 | + |
| 274 | + ui-- "update view" -->ui |
| 275 | + |
| 276 | + ui-- manipulate -->globconf |
| 277 | + ui-- manipulate --> profile |
| 278 | + ui-- manipulate --> scenecol |
| 279 | + scenecol-- notify -->ui |
| 280 | +``` |
| 281 | +
|
| 282 | +## "*OBS Controller*" and "*OBS Studio UI*" |
| 283 | +
|
| 284 | +"*OBS Studio UI*" act as "*OBS View*" and "*OBS Controller*". "*OBS Controller*" is scattered in various elements of the UI. |
| 285 | +
|
| 286 | +```mermaid |
| 287 | +flowchart LR |
| 288 | + user(["User(s)"]) |
| 289 | +
|
| 290 | + ui("OBS Studio UI") |
| 291 | +
|
| 292 | + globconf["globalConfig (OBSGlobalConfig)"] |
| 293 | + profile["profile (OBSProfile)"] |
| 294 | + scenecol["sceneCollection (OBSSceneCollection)"] |
| 295 | +
|
| 296 | + user-- "interact with" -->ui |
| 297 | + ui-- "user action" -->ui |
| 298 | + ui-- "update view" -->ui |
| 299 | +
|
| 300 | + ui-- manipulate -->globconf |
| 301 | + ui-- manipulate --> profile |
| 302 | + ui-- manipulate --> scenecol |
| 303 | + scenecol-- notify -->ui |
| 304 | +``` |
| 305 | +--- |
| 306 | + |
| 307 | +*OBS Controller* shall be split from the UI: |
| 308 | + |
| 309 | +```mermaid |
| 310 | +flowchart LR |
| 311 | + user(["User(s)"]) |
| 312 | +
|
| 313 | + control["OBSController"] |
| 314 | +
|
| 315 | + ui("OBS Studio UI") |
| 316 | +
|
| 317 | + globconf["globalConfig (OBSGlobalConfig)"] |
| 318 | + profile["profile (OBSProfile)"] |
| 319 | + scenecol["sceneCollection (OBSSceneCollection)"] |
| 320 | +
|
| 321 | + user-- "interact with" -->ui |
| 322 | + ui-- "user action" -->control |
| 323 | + control-- "update view" -->ui |
| 324 | +
|
| 325 | + control-- manipulate -->globconf |
| 326 | + control-- manipulate --> profile |
| 327 | + control-- manipulate --> scenecol |
| 328 | + scenecol-- notify -->control |
| 329 | + |
| 330 | +``` |
| 331 | + |
| 332 | +Other windows (e.g. settings) will parented to the main window but will be created from "*OBSController*". |
| 333 | + |
| 334 | +--- |
| 335 | + |
| 336 | +Note that OBSController will manage all OBSSignal and bound them to a Qt signal/slot meant for the UI, no OBSSignal will be directly bound to an UI element. |
| 337 | + |
| 338 | +```mermaid |
| 339 | +flowchart LR |
| 340 | + ui("OBS Studio UI") |
| 341 | + control["OBSController"] |
| 342 | + scenecol["OBSSceneCollection"] |
| 343 | +
|
| 344 | + ui-- "1. QSignal to QSlot" -->control |
| 345 | + control-- "4. QSlot from QSignal" -->ui |
| 346 | + control-- "2. manipulate" -->scenecol |
| 347 | + scenecol-- "3. OBSSignal" -->control |
| 348 | +``` |
| 349 | + |
| 350 | +# Additional Information |
| 351 | + |
| 352 | +The frontend API is the only part of OBS Studio that have access to "*OBS View*" (dock, action…), "*OBS Controller*" and "*OBS Model*" (sources, scenes…). |
| 353 | + |
| 354 | +The "OBSBasic" naming could retired from while progressing on the application of this RFC. |
0 commit comments