java-topology/defects/sdl3/unit/SDL3Test.java

164 lines
6.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* SDL3 CWE-407 benchmark: HasMappingChangeTracking linear scan vs O(1) hash set.
*
* Defect: src/joystick/SDL_gamepad.c HasMappingChangeTracking() lines 639-651
* Called inside PopMappingChangeTracking() for every joystick (line 670-687).
* changed_mappings is a pointer array scanned linearly each call.
* Total cost: O(n_joysticks × n_changed_mappings).
*
* Fix: Mirror changed_mappings into a SDL_HashTable (pointer set).
* HasMappingChangeTracking becomes one SDL_FindInHashTable call — O(1).
* SDL3 already uses SDL_HashTable for s_gamepadInstanceIDs in the same file.
*
* ticket: docs/tickets/sdl3-0001-gamepad-mapping-change-tracking-linear-scan.md
*/
public class SDL3Test {
// -----------------------------------------------------------------------
// SLOW: mirrors HasMappingChangeTracking — linear scan over pointer array
// -----------------------------------------------------------------------
static class SlowMappingChangeTracker {
List<Long> changedMappings = new ArrayList<>(); // Long = simulated pointer
void addMapping(long mappingPtr) {
changedMappings.add(mappingPtr);
}
/** Direct mirror of SDL_gamepad.c lines 645-649. */
boolean hasMappingChange(long mappingPtr) {
for (long m : changedMappings) {
if (m == mappingPtr) return true;
}
return false;
}
/** PopMappingChangeTracking inner loop: O(n_joysticks * n_changed_mappings). */
int processJoysticks(long[] joystickMappings) {
int remapped = 0;
for (long jMapping : joystickMappings) {
if (hasMappingChange(jMapping)) {
remapped++;
}
}
return remapped;
}
}
// -----------------------------------------------------------------------
// FAST: O(1) via HashSet (mirrors proposed SDL_HashTable fix)
// -----------------------------------------------------------------------
static class FastMappingChangeTracker {
Set<Long> changedMappingsSet = new HashSet<>();
void addMapping(long mappingPtr) {
changedMappingsSet.add(mappingPtr);
}
boolean hasMappingChange(long mappingPtr) {
return changedMappingsSet.contains(mappingPtr);
}
int processJoysticks(long[] joystickMappings) {
int remapped = 0;
for (long jMapping : joystickMappings) {
if (hasMappingChange(jMapping)) {
remapped++;
}
}
return remapped;
}
}
// -----------------------------------------------------------------------
// Bench harness
// -----------------------------------------------------------------------
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
slow.run(); fast.run();
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
double r = fOps > 0 ? (double) sOps / fOps : 0;
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, r);
}
// -----------------------------------------------------------------------
// Scenarios
// -----------------------------------------------------------------------
/**
* Scenario 1: Bulk mapping reload (SDL_AddGamepadMappingsFromFile with full DB).
* M changed mappings × J joysticks (J = 8, simulating a haptics rig).
*/
static Runnable slowBulkReload(int m, int j) {
return () -> {
SlowMappingChangeTracker tracker = new SlowMappingChangeTracker();
for (int i = 0; i < m; i++) tracker.addMapping((long) (i + 1));
// Each joystick has a mapping pointer in [1..m]
long[] jMappings = new long[j];
for (int i = 0; i < j; i++) jMappings[i] = (long) (i % m + 1);
tracker.processJoysticks(jMappings);
};
}
static Runnable fastBulkReload(int m, int j) {
return () -> {
FastMappingChangeTracker tracker = new FastMappingChangeTracker();
for (int i = 0; i < m; i++) tracker.addMapping((long) (i + 1));
long[] jMappings = new long[j];
for (int i = 0; i < j; i++) jMappings[i] = (long) (i % m + 1);
tracker.processJoysticks(jMappings);
};
}
/**
* Scenario 2: Full cross-product stress — M changed mappings × J joysticks,
* simulating a large tournament rig with many controllers and a full DB swap.
*/
static Runnable slowStress(int m, int j) {
return () -> {
SlowMappingChangeTracker tracker = new SlowMappingChangeTracker();
for (int i = 0; i < m; i++) tracker.addMapping((long) (i + 1));
long[] jMappings = new long[j];
// worst case: all joystick mappings are near the end of the array
for (int i = 0; i < j; i++) jMappings[i] = (long) (m - (i % 4) - 1);
tracker.processJoysticks(jMappings);
};
}
static Runnable fastStress(int m, int j) {
return () -> {
FastMappingChangeTracker tracker = new FastMappingChangeTracker();
for (int i = 0; i < m; i++) tracker.addMapping((long) (i + 1));
long[] jMappings = new long[j];
for (int i = 0; i < j; i++) jMappings[i] = (long) (m - (i % 4) - 1);
tracker.processJoysticks(jMappings);
};
}
public static void main(String[] args) {
System.out.println("SDL3 CWE-407: HasMappingChangeTracking linear scan vs O(1) hash set");
System.out.println(" defect: src/joystick/SDL_gamepad.c lines 639-651, 687");
System.out.println();
// SDL_gamepad_db.h ships 812 entries; simulate that scale
int M = 800; // changed mappings (full DB reload)
int J = 800; // joystick count (stress scenario)
long slowOps = (long) M * J;
long fastOps = J; // O(1) per joystick
bench(String.format("bulk-reload M=%d mappings J=8 joysticks", M),
slowBulkReload(M, 8), fastBulkReload(M, 8), (long) M * 8, 8);
bench(String.format("stress M=%d mappings J=%d joysticks", M, J),
slowStress(M, J), fastStress(M, J), slowOps, fastOps);
System.out.println();
System.out.println("Fix: add SDL_HashTable *changed_mappings_set to MappingChangeTracker.");
System.out.println(" SDL3 already has SDL_HashPointer/SDL_KeyMatchPointer.");
System.out.println(" See patch sdl3-0001-mapping-change-hash-set.patch");
}
}