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 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 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"); } }