GPC Developer Guides

Advanced Samples

# Bit-Packing SPVARs

This sample shows how you can save data across multiple SPVARs when needed and utilize the SPVARs in the most efficient way possible.

GPC

1// Define 6 variables to use in this example
2int var1, var2, var3, var4, var5, var6;
3
4init {
5 Load(); // Load our settings from flash or set the defaults
6}
7
8main {
9
10 // Display the various values in TRACE_1 to TRACE_6 so we can see what happens when we press the buttons below
11 set_val(TRACE_1, var1);
12 set_val(TRACE_2, var2);
13 set_val(TRACE_3, var3);
14 set_val(TRACE_4, var4);
15 set_val(TRACE_5, var5);
16 set_val(TRACE_6, var6);
17
18 // Press Right on the d-pad to increment the values
19 if(event_press(PS4_RIGHT)) {
20 var1 = clamp(var1 + 1, 0, 30);
21 var2 = clamp(var2 + 20, 0, 30000);
22 var3 = clamp(var3 + 10, -99, 99);
23 var4 = clamp(var4 + 1, 0, 50);
24 var5 = clamp(var5 + 40, -200, 200);
25 var6 = clamp(var6 + 10, 0, 60);
26 }
27 // Press Left on the d-pad to decrement the values
28 if(event_press(PS4_LEFT)) {
29 var1 = clamp(var1 - 1, 0, 30);
30 var2 = clamp(var2 - 20, 0, 30000);
31 var3 = clamp(var3 - 10, -99, 99);
32 var4 = clamp(var4 - 1, 0, 50);
33 var5 = clamp(var5 - 40, -200, 200);
34 var6 = clamp(var6 - 10, 0, 60);
35 }
36
37 // Press A/Cross to save
38 if(event_press(PS4_CROSS)){
39 Save();
40 }
41 // Press X/Square to load
42 if(event_press(PS4_SQUARE)){
43 Load();
44 }
45}
46// This is an example of how this can be used for loading values - NOTE: the ranges here must match what you have in your save function!
47function Load() {
48 reset_spvar(); // Always reset the spvar state before reading to ensure that we're reading from the same location as we last saved
49 if (read_spvar(0, 1, 0)) { // Read and check the first bit, if it's set, we know something should've been saved, otherwise we fall back on our default setting
50 var1 = read_spvar( 0, 30, 0); // Read var1 with a value within the range 0 to 30 with a default value of 0
51 var2 = read_spvar( 0, 30000, 0); // Read var2 with a value within the range 0 to 30000 with a default value of 0
52 var3 = read_spvar( -99, 99, 0); // Read var3 with a value within the range -99 to 99 with a default value of 0
53 var4 = read_spvar( 0, 50, 0); // Read var4 with a value within the range 0 to 50 with a default value of 0
54 var5 = read_spvar(-200, 200, 0); // Read var5 with a value within the range -200 to 200 with a default value of 0
55 var6 = read_spvar( 0, 60, 0); // Read var6 with a value within the range 0 to 60 with a default value of 0
56 }
57 else {
58 var1 = 1; // Set var1 to it's default value of 1
59 var2 = 2; // Set var2 to it's default value of 2
60 var3 = 5; // Set var3 to it's default value of 5
61 var4 = 4; // Set var4 to it's default value of 4
62 var5 = 5; // Set var5 to it's default value of 5
63 var6 = 6; // Set var6 to it's default value of 6
64 }
65}
66
67// This is an example of how this can be used for saving values - NOTE: the ranges here must match what you have in your load function!
68function Save(){
69 reset_spvar(); // Always reset the spvar state before saving to ensure that we're saving at the same location as we will later read
70 save_spvar( 1, 0, 1); // Save a constant 1 to denote previously saved data, this range uses 1 bit
71 // At this point we're using 1 bit in SPVAR_1
72 save_spvar(var1, 0, 30); // Save var1 with a range between 0 and 30, this range uses 5 bits
73 // At this point we're using 6 bits in SPVAR_1
74 save_spvar(var2, 0, 30000); // Save var2 with a range between -0 and 30000, this range uses 15 bits
75 // At this point we're using 21 bits in SPVAR_1
76 save_spvar(var3, -99, 99); // Save var3 with a range between -99 and 99, this range uses 8 bits
77 // At this point we're using 29 bits in SPVAR_1
78 save_spvar(var4, 0, 50); // Save var4 with a range between 0 and 50, this range uses 6 bits
79 // At this point we're using 32 bits in SPVAR_1 and 3 bits in SPVAR_2 - var4 is saved across both SPVAR_1 (the last 3 bits) and SPVAR_2 (the first 3 bits)
80 save_spvar(var5, -200, 200); // Save var5 with a range between -200 and 200, this range uses 9 bits
81 // At this point we're using 32 bits in SPVAR_1 and 18 bits in SPVAR_2
82 save_spvar(var6, 0, 60); // Save var6 with a range between 0 and 60, this range uses 6 bits
83 // At this point we're using 32 bits in SPVAR_1 and 24 bits in SPVAR_2
84}
85// Function used to reset the SPVAR state to where we begin, this one you can change if you like, the rest you should leave as-is or you risk breaking the logic of this. YOU HAVE BEEN WARNED!
86function reset_spvar() {
87 spvar_current_slot = SPVAR_1; // Change this to say where it's safe to start storing data
88 spvar_current_bit = 0; // Should always be 0, unless you're using part of the first SPVAR in which case you should also change the next line to include the value you are storing in the bits you are using
89 spvar_current_value = 0;
90}
91
92// ------ DO NOT TOUCH ANYTHING BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING! ------
93
94int spvar_current_bit, // Variable used to keep track of the next available bit
95 spvar_current_slot, // Variable used to keep track of the currently used SPVAR slot
96 spvar_current_value, // Variable used to keep track of the current value with all the bits from the previous variables saved in the current SPVAR
97 spvar_tmp, // Variable used temporarily during the various calculation steps
98 spvar_bits; // Variable used to keep track of the number of bits required to represent the currently saved/loaded variable
99
100// Function used to count the number of bits used by the given value
101function get_bit_count(val) {
102 spvar_tmp = 0; // We need to start at 0, we use spvar_tmp here as we need to track the bits during our loop below
103 while (val) { // Loop while val is anything but 0
104 spvar_tmp++; // Increment the bit count by 1
105 val = abs(val >> 1); // Shift the value down 1 bit, once we have no more bits set this will result in 0, unless the value is negative - in which case this will be endless, we do abs here to make it always
106 }
107 return spvar_tmp;
108}
109// Function used to count the number of bits used by 2 given values
110function get_bit_count2(val1, val2) {
111 spvar_tmp = max(get_bit_count(val1), get_bit_count(val2)); // Get the highest bit count required for either min or max
112 if (is_signed2(val1, val2)) { // Check if we need to know if the value is negative or not
113 spvar_tmp++; // If we need to track if the saved value is negative, we need 1 bit for that specifically - the others are used to store the actual value
114 }
115 return spvar_tmp;
116}
117// Function used to determine if either of 2 given values is negative
118function is_signed2(val1, val2) { return val1 < 0 || val2 < 0; }
119// Function used to generate a bitmask for the sign bit, this will always be the highest bit in the range we're requesting it for, to do that - we need to start with the lowest bit set and move it up the number of steps there is between 1 and the bits we need, this needs to be a maximum of 31 but can never be negative
120function make_sign(bits) { return 1 << clamp(bits - 1, 0, 31); }
121// Function used to generate a full bitmask (essentially all bits set up to and including the number of bits given)
122function make_full_mask(bits) {
123 if (bits == 32) { // If we're wanting a bitmask for all bits, we can simply return -1 (which is all bits set to 1)
124 return -1;
125 }
126 return 0x7FFFFFFF >> (31 - bits); // What we do here is basically take a value with all bits except the highest set and shift them down as many times as we need to get a mask that fits the bit count we're looking for
127}
128// Function used to generate a bitmask for just the bits required for the value part of a signed range, this means all the bits below the sign bit
129function make_sign_mask(bits) { return make_full_mask(bits - 1); }
130// Function used to pack a value that has potential for being negative in a way that we use the least number of bits we really need to represent the value
131function pack_i(val, bits) {
132 if (val < 0) { // Check if we have a negative value, if so - handle it accordingly
133 return (abs(val) & make_sign_mask(bits)) | make_sign(bits); // Get the positive version of the value and keep the bits that are within range of what we're doing and add the sign bit since we have a negative value and return the result
134 }
135 return val & make_sign_mask(bits); // Get the bits that are within our range
136}
137// Function used to unpack (restore) a value that has potential for being negative, essentially reversing what pack_i does above
138function unpack_i(val, bits) {
139 if (val & make_sign(bits)) { // Check if the stored value is supposed to ve negative
140 return 0 - (val & make_sign_mask(bits)); // Retrieve the stored positive value and subtract it from 0 (resulting in the same value except negative), return the result
141 }
142 return val & make_sign_mask(bits); // Retrieve the stored positive value and return it
143}
144// Function used to read the value of a SPVAR without any limits
145function read_spvar_slot(slot) { return get_pvar(slot, 0x80000000, 0x7FFFFFFF, 0); }
146// Function used to save your value in the SPVARs, this is the function you'll be calling when saving a value. You need to provide the value to save aswell as the range (minimum and maximum value, this is how we determine how many bits to use when saving this value)
147function save_spvar(val, min, max) {
148 spvar_bits = get_bit_count2(min, max); // Set spvar_bits to the number of bits we need for this range
149
150 val = clamp(val, min, max); // Make sure the value is within our defined range to begin with
151
152 if (is_signed2(min, max)) { // If either min or max is negative, we need to pack this value as a possibly negative value
153 val = pack_i(val, spvar_bits); // Pack as signed value (possibly negative)
154 }
155 val = val & make_full_mask(spvar_bits); // Pack as unsigned value (always positive), this essentially just makes the resulting value not have any extra bits set - it's safe to use after the signed packing since we're not using any bits outside of the unsigned range anyways
156
157 if (spvar_bits >= 32 - spvar_current_bit) { // Check if there is not enough bits remaining to save this value as-is. if there aren't enough bits, we save what we can here and store the remaining bits in the next spvar, if this means we're hitting the end, we can make this smaller by handling the case where we use all bits here aswell
158 spvar_current_value = spvar_current_value | (val << spvar_current_bit); // Add what we can to the current value where there is bits available to use
159 set_pvar(spvar_current_slot, spvar_current_value); // Save the current SPVAR before advancing to the next one
160 spvar_current_slot++; // Move to the next slot
161 spvar_bits -= (32 - spvar_current_bit); // Update the required bits according to our needs for the next slot, if we don't do this here, we'll screw up the saved value by moving it too far out of range
162 val = val >> (32 - spvar_current_bit); // Move the remaining bits down, discarding the bits we've already saved
163 spvar_current_bit = 0; // Reset the current bit counter since we're starting with a new SPVAR
164 spvar_current_value = 0; // Reset our value so we start clean, we aren't currently using any bits anyways
165 }
166
167 spvar_current_value = spvar_current_value | (val << spvar_current_bit); // Merge the current SPVAR value with our currently value where there is space to keep our value
168 spvar_current_bit += spvar_bits; // Move up the counter of next available bit to where we are currently saving data at
169 if (!spvar_current_bit) {
170 spvar_current_value = 0; // Reset our value so we start clean, we aren't currently using any bits anyways
171 }
172 set_pvar(spvar_current_slot, spvar_current_value); // Save the SPVAR with the current value, this won't write anything to flash unless the value changed - so we can do this for each variable saved to no risk missing anything
173}
174// Function used to read your value from the SPVARs, this is the function you'll be calling when reading a value. You need to provide the range (minimum and maximum value, this is how we determine how many bits to use when reading the value) aswell as a default value if what we read is out of range
175function read_spvar(min, max, def) {
176 spvar_bits = get_bit_count2(min, max); // Set spvar_bits to the number of bits we need for this range
177 spvar_current_value = (read_spvar_slot(spvar_current_slot) >> spvar_current_bit) & make_full_mask(spvar_bits); // Read the current SPVAR value from flash and shift them into position, we'll handle split values next
178
179 if (spvar_bits >= 32 - spvar_current_bit) { // Check if we are dealing with a split SPVAR value, essentially if the current position means we're using more than 32 bits in the SPVAR, we need to retrieve the missing bits from the next SPVAR and put them back to our current value, we use the same space saving trick here as in the save function
180 spvar_current_value = (spvar_current_value & make_full_mask(32 - spvar_current_bit)) | ((read_spvar_slot(spvar_current_slot + 1) & make_full_mask(spvar_bits - (32 - spvar_current_bit))) << (32 - spvar_current_bit));
181 //Below is a breakdown of the line above, with each step done one at a time instead of all at once - this however increases codesize - the below code is to explain how it all works tho
182 //spvar_tmp = read_spvar_slot(spvar_current_slot + 1); // Read the SPVAR slot coming after the initial one we used to spvar_tmp from flash, we need to maintain the data we've read thus far, but also add on what we have in flash for the next SPVAR
183 //spvar_tmp = spvar_tmp & make_full_mask(spvar_bits - (32 - spvar_current_bit)); // Extract the bits we need need (the ones that didn't fit in the previous SPVAR)
184 //spvar_tmp = spvar_tmp << (32 - spvar_current_bit); // Move the bits into their original position, they were stored at the beginning of the new SPVAR but belong at the top of the currently read value
185 //spvar_current_value = (spvar_current_value & make_full_mask(32 - spvar_current_bit)) | spvar_tmp; // put all bits together again with the part read from the first SPVAR cleaned up to only include the bits from this variable/value and not all bits set in the upper range like they normally are
186 }
187 spvar_current_bit += spvar_bits; // Move up the counter of next available bit to where we are will be reading data from next
188 spvar_current_value = spvar_current_value & make_full_mask(spvar_bits); // Extract all bits included for this value and discard any other bits
189 if (spvar_current_bit >= 32) {
190 spvar_current_slot++; // Move to the next SPVAR slot
191 spvar_current_bit -= 32; // Remove 32 from the spvar_current_bit tracker since we've gone beyond what we can do here
192 }
193
194 if (is_signed2(min, max)) { // Check if the value can be negative and handle it accordingly
195 spvar_current_value = unpack_i(spvar_current_value, spvar_bits); // Restore the signed, possibly negative value
196 }
197
198 if (spvar_current_value < min || spvar_current_value > max) { // Check if the value is below our specified min or above our specified max, if so - return the default value instead
199 return def; // This can be changed to min instead as a reasonable default with the default parameter being removed if you don't need to have a override value for the default when out of range, that will save a bit of code size
200 }
201
202 // Return the retrieved value to the user since it's within the expected range
203 return spvar_current_value;
204}