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