|
| 1 | +#name "Settings advanced example" |
| 2 | +#author "Phlarx" |
| 3 | +#category "Examples" |
| 4 | + |
| 5 | +/* This plugin demonstrates some techniques for storing |
| 6 | + * data with the Settings interface, when the data types |
| 7 | + * are not supported by the Setting annotation. |
| 8 | + * |
| 9 | + * In this demonstration, we will be storing vec3 objects, |
| 10 | + * since they are not supported by the Setting annotation |
| 11 | + * directly, but they are still simple enough to be a |
| 12 | + * good example. |
| 13 | + * |
| 14 | + * One shortcoming of this example is that it is rather |
| 15 | + * cheap and easy to construct vec3 objects on the fly, |
| 16 | + * where needed. These techniques are most useful when |
| 17 | + * applied to objects that are more expensive to construct. |
| 18 | + * |
| 19 | + * These examples are not intended to be the bible for |
| 20 | + * achieving these effects, but instead a set of ideas and |
| 21 | + * inspirations that you can apply to your use case. The |
| 22 | + * strategies used here can be mixed and matched, and built |
| 23 | + * upon. |
| 24 | + * |
| 25 | + * Finally, larger data structures may benefit more from |
| 26 | + * being stored separately in their own data file, instead |
| 27 | + * of being shoehorned into the Settings ini file. Take note |
| 28 | + * of this when deciding which strategy is best for your |
| 29 | + * specific application. |
| 30 | + */ |
| 31 | + |
| 32 | +/* Our goal for each vec3 is to approximate the following |
| 33 | + * (invalid) code: |
| 34 | + * |
| 35 | + * [Setting] |
| 36 | + * vec3 Data; |
| 37 | + * |
| 38 | + * Each example has: |
| 39 | + * - a declaration, which defines the objects that we want |
| 40 | + * to store. |
| 41 | + * - getters and setters which are able to store the object |
| 42 | + * into the settings interface, and retrieve it again. |
| 43 | + * - accessors and mutators which are the script's normal |
| 44 | + * interactions with the objects. |
| 45 | + * |
| 46 | + * In addition, the third example, called Qux, has a |
| 47 | + * separate pair of functions for data conversion. The other |
| 48 | + * two examples do their data conversion at the same place as |
| 49 | + * one or more of the parts listed above. |
| 50 | + */ |
| 51 | + |
| 52 | +/* [[FooColor declaration]] |
| 53 | + * The first vec3, Foo, will be stored by creating separate |
| 54 | + * entries for each component. This allows the user to set |
| 55 | + * these values within the Settings dialog. Since the Setting |
| 56 | + * annotation doesn't provide the facilities to automatically |
| 57 | + * synchonize the vec3 directly, we'll need to use both |
| 58 | + * OnSettingsChanged and OnSettingsLoad to get the updates |
| 59 | + * via the Settings interface, as well as manually change the |
| 60 | + * component values when the vec3 is updated. |
| 61 | + * |
| 62 | + * Here, we see each float component declared individually, |
| 63 | + * with a Setting annotation for each, followed by the vec3 |
| 64 | + * declaration. |
| 65 | + */ |
| 66 | +[Setting min=0.f max=1.f] |
| 67 | +float FooR = 1.f; |
| 68 | +[Setting min=0.f max=1.f] |
| 69 | +float FooG = 0.f; |
| 70 | +[Setting min=0.f max=1.f] |
| 71 | +float FooB = 0.f; |
| 72 | +vec3 FooColor = vec3(FooR, FooG, FooB); |
| 73 | + |
| 74 | +/* [[BarColor declaration]] |
| 75 | + * The second vec3, Bar, does not use separated component values, |
| 76 | + * and as such, it will not appear in the Settings dialog. As a |
| 77 | + * result, we need to only declare the actual vec3 object. |
| 78 | + */ |
| 79 | +vec3 BarColor = vec3(0.f, 1.f, 0.f); |
| 80 | + |
| 81 | +/* [[QuxColor declaration]] |
| 82 | + * The third vec3 is Qux, and like Bar, is does not appear in the |
| 83 | + * Settings dialog, and therefore we only need to declare the vec3. |
| 84 | + */ |
| 85 | +vec3 QuxColor = vec3(0.f, 0.f, 1.f); |
| 86 | + |
| 87 | +/* OnSettingsChanged is called whenever an update occurs within the |
| 88 | + * Settings dialog. |
| 89 | + */ |
| 90 | +void OnSettingsChanged() |
| 91 | +{ |
| 92 | + /* [[FooColor get value from components]] |
| 93 | + * Since Foo is the only strategy which exposes the component |
| 94 | + * values to the Settings dialog, it is the only one which needs |
| 95 | + * to respond to that event. As the vec3 case is rather simple, |
| 96 | + * we can just immediately update the component values. |
| 97 | + */ |
| 98 | + FooColor.x = FooR; |
| 99 | + FooColor.y = FooG; |
| 100 | + FooColor.z = FooB; |
| 101 | +} |
| 102 | + |
| 103 | +/* OnSettingsSave is called when stopping a plugin, which may be |
| 104 | + * caused by, for example, reloading the plugin or exiting the |
| 105 | + * game. |
| 106 | + * |
| 107 | + * Note that the settings data is only written to the disk when |
| 108 | + * exiting the game, and is cached otherwise. |
| 109 | + */ |
| 110 | +void OnSettingsSave(Settings::Section& section) |
| 111 | +{ |
| 112 | + /* [[BarColor set value to Settings]] |
| 113 | + * Here, we manually set the component values for Bar to its |
| 114 | + * component values. In the Settings ini file, this will look |
| 115 | + * almost identical to the approach for Foo, but by using this |
| 116 | + * route, the components do not appear within the settings menu, |
| 117 | + * and we don't need to worry about keeping the values |
| 118 | + * synchonized. |
| 119 | + * |
| 120 | + * As a side effect of doing this ourselves, values identical to |
| 121 | + * the default are not omitted from the ini, in contrast to Foo. |
| 122 | + */ |
| 123 | + section.SetFloat("BarR", BarColor.x); |
| 124 | + section.SetFloat("BarG", BarColor.y); |
| 125 | + section.SetFloat("BarB", BarColor.z); |
| 126 | + |
| 127 | + /* [[QuxColor set value to Settings]] |
| 128 | + * For Qux, we use a similar approach to Bar by setting the |
| 129 | + * Settings ini values directly, but in this case we've used a |
| 130 | + * conversion method to convert the value of Qux to some data |
| 131 | + * type matively handled by the settings interface. In this case, |
| 132 | + * we are using a string holding JSON data. The definition of |
| 133 | + * writeQux is found near the bottom of this file. |
| 134 | + */ |
| 135 | + section.SetString("Qux", writeQux(QuxColor)); |
| 136 | +} |
| 137 | + |
| 138 | +/* OnSettingsLoad is called when starting a plugin, which may be |
| 139 | + * caused by, for example, reloading the plugin or launching the |
| 140 | + * game. |
| 141 | + * |
| 142 | + * Note that the settings data is only read from the disk when |
| 143 | + * launching the game, and is cached otherwise. |
| 144 | + */ |
| 145 | +void OnSettingsLoad(Settings::Section& section) |
| 146 | +{ |
| 147 | + /* [[FooColor get value from components]] |
| 148 | + * Since OnSettingsChanged is not triggered at plugin startup, |
| 149 | + * we replicate the actions for Foo here. |
| 150 | + */ |
| 151 | + FooColor.x = FooR; |
| 152 | + FooColor.y = FooG; |
| 153 | + FooColor.z = FooB; |
| 154 | + |
| 155 | + /* [[BarColor get value from Settings]] |
| 156 | + * When loading the values from the settings interface, we are |
| 157 | + * simply performing the inverse of what we had done in |
| 158 | + * OnSettingsSave. Additionally, we can provide default values |
| 159 | + * to use for each component, if that component is found to be |
| 160 | + * missing. |
| 161 | + */ |
| 162 | + BarColor.x = section.GetFloat("BarR", 0.f); |
| 163 | + BarColor.y = section.GetFloat("BarG", 1.f); |
| 164 | + BarColor.z = section.GetFloat("BarB", 0.f); |
| 165 | + |
| 166 | + /* [[QuxColor get value from Settings]] |
| 167 | + * In the case of Qux, the default value is the full json |
| 168 | + * description of the object, and the result of the Settings |
| 169 | + * load is passed through a converter to create the actual |
| 170 | + * object that we want. The definition of parseQux is found |
| 171 | + * near the bottom of this file. |
| 172 | + */ |
| 173 | + QuxColor = parseQux(section.GetString("Qux", "{'r':0,'g':0,'b':1}")); |
| 174 | +} |
| 175 | + |
| 176 | +void RenderInterface() |
| 177 | +{ |
| 178 | + UI::Begin("Settings Advanced", UI::WindowFlags::AlwaysAutoResize); |
| 179 | + |
| 180 | + /* [[FooColor accesses and mutations]] |
| 181 | + * Our strategy for Foo shows real-time updates in the Settings |
| 182 | + * dialog, so we need to manually update the individual |
| 183 | + * components whenever Foo is updated. |
| 184 | + */ |
| 185 | + FooColor = UI::InputColor3("Foo Color", FooColor); |
| 186 | + FooR = FooColor.x; |
| 187 | + FooG = FooColor.y; |
| 188 | + FooB = FooColor.z; |
| 189 | + |
| 190 | + /* [[BarColor accesses and mutations]] |
| 191 | + * Since our strategy for Bar does not require updating the stored |
| 192 | + * values for use in the Settings dialog, we can use both direct |
| 193 | + * accesses and mutations. |
| 194 | + */ |
| 195 | + BarColor = UI::InputColor3("Bar Color", BarColor); |
| 196 | + |
| 197 | + /* [[QuxColor accesses and mutations]] |
| 198 | + * Like Bar, for Qux we can use both direct accesses and mutations. |
| 199 | + */ |
| 200 | + QuxColor = UI::InputColor3("Qux Color", QuxColor); |
| 201 | + |
| 202 | + UI::End(); |
| 203 | +} |
| 204 | + |
| 205 | +/* [[QuxColor datatype conversion]] |
| 206 | + * Where the component decomposition of Foo and Bar are scattered |
| 207 | + * in several placed throughout the source, the breakdown for Qux |
| 208 | + * is restricted to just the two conversion functions, parseQux and |
| 209 | + * writeQux. |
| 210 | + * |
| 211 | + * This particular implementation makes use of the Json group within |
| 212 | + * the Openplanet API, but a similar effect can be achieved with XML, |
| 213 | + * a bespoke serialization format, a unique identifier, or any of a |
| 214 | + * number of other things. |
| 215 | + |
| 216 | + * We need both directions for this conversion, and so writeQux is |
| 217 | + * the inverse operation of parseQux. To say it another way, |
| 218 | + * color == parseQux(writeQux(color)). |
| 219 | + */ |
| 220 | +vec3 parseQux(string json) |
| 221 | +{ |
| 222 | + Json::Value obj = Json::Parse(json); |
| 223 | + vec3 color; |
| 224 | + color.x = obj.Get("r", 0.f); |
| 225 | + color.y = obj.Get("g", 0.f); |
| 226 | + color.z = obj.Get("b", 1.f); |
| 227 | + return color; |
| 228 | +} |
| 229 | + |
| 230 | +string writeQux(vec3 color) |
| 231 | +{ |
| 232 | + Json::Value obj = Json::Object(); |
| 233 | + obj["r"] = Json::Value(color.x); |
| 234 | + obj["g"] = Json::Value(color.y); |
| 235 | + obj["b"] = Json::Value(color.z); |
| 236 | + return Json::Write(obj); |
| 237 | +} |
0 commit comments