3e8fd317c7e9986068ed28dae78e8606360ce044
[xonotic/netradiant.git] / libs / synapse / doc / design.txt
1 synapse code design documentation\r
2 =================================\r
3 \r
4 Objective:\r
5 ----------\r
6 \r
7 Provide a simple cross platform layer to use dynamically loaded code\r
8 inside a core application. Portability intended to win32 / linux / MacOS (?)\r
9 \r
10 Main features are:\r
11 \r
12 - designed for single process only, no remote clients, no asynchronous processes\r
13 - a client/server architecture, based on configuration files: a main binary,\r
14   loading a set of shared objects\r
15   \r
16 Constraints:\r
17 ------------\r
18 \r
19 - large existing plugin code in Radiant!\r
20   must be compatible with minimal changes, specially for plugins (i.e. clients)\r
21 \r
22 - make things as much transparent as possible\r
23   (ideally, no real difference between a static linkage and dynamic linkage,\r
24   cf usage of #define macros to wrap a function call onto a code pointer)\r
25   \r
26 Features:\r
27 ---------\r
28 \r
29 Gather as much generic code as possible in a static .lib with minimal dependencies\r
30 (only dependency should be configuration files parser)\r
31 \r
32 NOTE: current effective dependency is STL / glib / xml\r
33 \r
34 Main executable implemented as a server, all others as clients. What has to\r
35 be done for a server / what has to be done for a client needs to be documented.\r
36 Provide as much scripts and tools and guidelines as needed (scripted generation of\r
37 some .h files?)\r
38 \r
39 Proposed implementation:\r
40 ------------------------\r
41 \r
42 - have linux/ and win32/ subdirectories with OS-specific implementations\r
43 (such as dynamically loading shared objects, and doing the initial query?)\r
44 \r
45 - reduce the API of a client to the minimum: one exported function?\r
46 provide a squeleton to make new clients easily?\r
47 \r
48 Server use case:\r
49 1) build information about location of the modules (from code and config files)\r
50 2) load all modules and query information about their APIs\r
51 NOTE: could read the APIs from some XML description files instead of\r
52 querying it from the modules?\r
53 3) build information about the required function tables\r
54 i.e.: setup a list with the function tables to be filled in, and what they\r
55 need to be filled in with.\r
56 4) resolve the function table\r
57 NOTE: is this iterative? will some plugins request more APIs as they get filled\r
58 up?\r
59 NOTE: do we have optional tables?\r
60 5) unload unreferences modules\r
61 NOTE: we don't expect to be unloading a LOT of modules, otherwise we would\r
62 setup a solution that allows exploring of the APIs a given module provides\r
63 from a file description. Or you could 'cache' that (md5-checksum the file, and \r
64 maintain an XML list).\r
65 \r
66 Client use case:\r
67 1) dynamically loaded\r
68 2) prompted for the interfaces it provides\r
69 2) prompted for the interfaces it requires\r
70 3) either unloaded, or told what interfaces have been filled in\r
71 \r
72 The client module exports an Synapse_EnumerateInterfaces entry point\r
73 This returns an ISynapseClient, which lists what the plugin provides, and what it requires\r
74 \r
75 The APIs:\r
76 \r
77 An interface is a function table, GUID / major string / minor string\r
78 GUID is a shortcut to reference a major string (i.e. the human readable thing)\r
79 the GUID / major string is unique for a given interface\r
80 minor string is used to reference a particular version of an API\r
81   (for instance when talking about image loading functionality, tga and jpg etc.)\r
82   \r
83 The GUID scheme is handy because it provides easy tests. They are not strictly\r
84 necessary but we will probably want to keep them. Should we extend to GUIDs\r
85 for minor too?\r
86 \r
87 Roadmap:\r
88 --------\r
89 \r
90 Need to convert the core (as server) and the required modules. Will have\r
91 clearer view of what's to be done along the way.\r
92 \r
93 Implementation design:\r
94 ----------------------\r
95 \r
96 There is a client and server side to synapse. Typically server is in Radiant or q3map,\r
97 client is in any module. For implementation, we have one server class and one client class.\r
98 It would be possible to have two seperate libraries, synapse-client and synapse-server. But \r
99 that only brings down the statically linked stuff to make things more complicated build-sysem\r
100 wise.\r
101 \r
102 Initial implementation has been using isynapse.h and synapse.h, to provide a pure virtual\r
103 base class for server and client. But that doesn't bring any major functionality, it's easier\r
104 if both sides see the full API of the client and server classes.\r
105 \r
106 A side problem is the diagnostic printing functionality. For easy debugging we require that \r
107 the synapse code can have access to a Sys_Printf or similar function at all times (that is for \r
108 client and server implementation). On client we will pipe through the main API to the server \r
109 as soon as we can in most cases. Using Sys_Printf would bring us to a dead end situation, since\r
110 when synapse is used as the server, the main code implements it's own Sys_Printf stuff.\r
111 Instead we introduce a local Syn_Printf implementation, which can be overriden in the server\r
112 to point to the appropriate print functions.\r
113 \r
114 Runtime config:\r
115 ---------------\r
116 \r
117 Something that has not been looked upon a lot yet, runtime configuration. What interfaces\r
118 are loaded etc. Ideally, from an XML config file. A client explicitely requests the\r
119 server to load all the interfaces it requires (in this case, the client is radiant or\r
120 q3map).\r
121 \r
122 Plugins are somewhat out of the 'required interfaces' frame, since they are loaded \r
123 whenever they are found. It is possible however that some plugins would not want to be \r
124 loaded in if the game doesn't match etc. in case they would need to access the global \r
125 config?\r
126 \r
127 In most cases a given API is only required once for editor functionality. (VFS for \r
128 instance), so our #define strategy for easy mapping of the functions should still work.\r
129 \r
130 Version checks, reference counting:\r
131 ------------------------------------\r
132 \r
133 Need version checking at several levels. A version string (major/minor) on the main API\r
134 entry point (straight in the exported function to save as much as possible for \r
135 compatibility). For individual APIs, we have been feeding the struct size into the first\r
136 int of the struct so far, and it has worked very well.\r
137 \r
138 Reference counting: we introduced class based APIs to solve the ref counting issues,\r
139 which are not easy to solve on C function tables. That problem would arise in plugin\r
140 situations where we might want to 'reload' or 'unload' some plugins. The server could\r
141 keep track of the ref count.\r
142 \r
143 Caching?\r
144 --------\r
145 \r
146 We are going to load every shared object we find and query it for it's interfaces. Then\r
147 we will unload the stuff we don't want. This is going to slow down the startup process.\r
148 We could extract the API information in a cache to avoid the loading step.\r
149 \r
150 Interfaces with multiple minors against I* objects?\r
151 ---------------------------------------------------\r
152 \r
153 Looking at the iimage.h API, why not having instead something that enumerates C++ objects \r
154 directly? Mainly because we want to be able to spread several minors accross multiple modules\r
155 and still use them together. And straight laid out function tables in C structs are only\r
156 one indirection when the table is static.\r
157 \r
158 This raises a broader topic, instead of requesting APIs, we could request objects directly.\r
159 Would that be of any use?\r
160 \r
161 Loading interfaces / resolving interdependencies strategy\r
162 ---------------------------------------------------------\r
163 \r
164 Some notes about how we load the modules and resolve interdependencies:\r
165 \r
166 We want to avoid requesting a module for an API it provides before all the APIs it requires\r
167 have been filled in (mostly stability concerns, a module may be doing whatever internally \r
168 when we request something from it). The exception being the module we are trying to resolve\r
169 for (since we need a start point for resolution). But in all likelyness we resolve for radiant\r
170 or q3map for instance.\r
171 \r
172 With this approach, it is possible that some situations could not be resolved, for instance:\r
173 Radiant\r
174   requires A\r
175   provides B\r
176 module 1\r
177   requires C\r
178   provides A\r
179 module 2\r
180   requires A\r
181   provides C\r
182 if we start by resolving Radiant, we will get stuck\r
183 if we are ready to ask module to provide the API even though the required is not meant, it would work\r
184 but that kind of situation is very unlikely, so sticking to safer strategy\r
185   \r
186 Configuration\r
187 -------------\r
188 \r
189 the config info needs to go down to the clients too\r
190 for instance, mapxml loaded for q3map or radiant, doesn't rely on the same major?