/// <summary> /// Carries out a set of inputs. /// </summary> /// <param name="inputList">A list of lists of inputs to execute.</param> public static void CarryOutInput(List <List <Parser.Input> > inputList) { /*Kimimaru: We're using a thread pool for efficiency * Though very unlikely, there's a chance the input won't execute right away if it has to wait for a thread to be available * However, there are often plenty of available threads, so this shouldn't be an issue since we use only one thread per input string * Uncomment the following lines to see how many threads are supported in the pool on your machine */ //ThreadPool.GetMinThreads(out int workermin, out int completionmin); //ThreadPool.GetMaxThreads(out int workerthreads, out int completionPortThreads); //Console.WriteLine($"Min workers: {workermin} Max workers: {workerthreads} Min async IO threads: {completionmin} Max async IO threads: {completionPortThreads}"); //Kimimaru: Copy the input list over to an array, which is more performant //and lets us bypass redundant copying and bounds checks in certain instances //This matters once we've begun processing inputs since we're //trying to reduce the delay between pressing and releasing inputs as much as we can Parser.Input[][] inputArray = new Parser.Input[inputList.Count][]; for (int i = 0; i < inputArray.Length; i++) { inputArray[i] = inputList[i].ToArray(); } InputWrapper inputWrapper = new InputWrapper(inputArray); ThreadPool.QueueUserWorkItem(new WaitCallback(ExecuteInput), inputWrapper); }
//Kimimaru: Think of a faster way to do this; it's rather slow //The idea is to look through and see the inputs that would be held at the same time and avoid the given combo //One list is for held inputs with another for pressed inputs - the sum of their counts is compared with the invalid combo list's count //Released inputs do not count public static bool ValidateButtonCombos(List <List <Parser.Input> > inputs, List <string> invalidCombo) { int controllerCount = InputGlobals.ControllerMngr.ControllerCount; //These dictionaries are for each controller port Dictionary <int, List <string> > currentComboDict = new Dictionary <int, List <string> >(controllerCount); Dictionary <int, List <string> > subComboDict = new Dictionary <int, List <string> >(controllerCount); for (int i = 0; i < controllerCount; i++) { IVirtualController controller = InputGlobals.ControllerMngr.GetController(i); if (controller.IsAcquired == false) { continue; } //Add already pressed inputs from all controllers for (int j = 0; j < invalidCombo.Count; j++) { string button = invalidCombo[j]; if (controller.GetButtonState(InputGlobals.CurrentConsole.ButtonInputMap[button]) == ButtonStates.Pressed) { if (currentComboDict.ContainsKey(i) == false) { currentComboDict[i] = new List <string>(invalidCombo.Count); } currentComboDict[i].Add(button); } } } //If all these inputs are somehow pressed already, whatever we do now doesn't matter //However, returning false here would prevent any further inputs from working, so //give a chance to check other inputs (such as releasing) for (int i = 0; i < inputs.Count; i++) { List <Parser.Input> inputList = inputs[i]; //Clear sublists foreach (List <string> subList in subComboDict.Values) { subList.Clear(); } for (int j = 0; j < inputList.Count; j++) { Parser.Input input = inputList[j]; //Get controller port and initialize int port = input.controllerPort; //Ensure a currentcombo entry is available for this port if (currentComboDict.ContainsKey(port) == false) { currentComboDict[port] = new List <string>(invalidCombo.Count); } //Ensure a subcombo entry is available for this port if (subComboDict.ContainsKey(port) == false) { subComboDict[port] = new List <string>(invalidCombo.Count); } //Current and sub combo lists List <string> currentCombo = currentComboDict[port]; List <string> subCombo = subComboDict[port]; //Check if this input is in the invalid combo if (invalidCombo.Contains(input.name) == true) { //If it's not a release input and isn't in the held or current inputs, add it if (input.release == false && subCombo.Contains(input.name) == false && currentCombo.Contains(input.name) == false) { subCombo.Add(input.name); //Check the count after adding if ((subCombo.Count + currentCombo.Count) == invalidCombo.Count) { return(false); } } //For holds, use the held combo if (input.hold == true) { if (currentCombo.Contains(input.name) == false) { //Remove from the subcombo to avoid duplicates currentCombo.Add(input.name); subCombo.Remove(input.name); if ((currentCombo.Count + subCombo.Count) == invalidCombo.Count) { return(false); } } } //If released, remove from the current combo else if (input.release == true) { currentCombo.Remove(input.name); } } } } return(true); }
private static void ExecuteInput(object obj) { /************************************************************* * PERFORMANCE CRITICAL CODE * * Even the smallest change must be thoroughly tested * *************************************************************/ //Increment running threads Interlocked.Increment(ref RunningInputThreads); //Get the input list - this should have been validated beforehand InputWrapper inputWrapper = (InputWrapper)obj; Parser.Input[][] inputArray = inputWrapper.InputArray; Stopwatch sw = new Stopwatch(); List <int> indices = new List <int>(16); IVirtualControllerManager vcMngr = InputGlobals.ControllerMngr; int controllerCount = vcMngr.ControllerCount; int[] nonWaits = new int[controllerCount]; //This is used to track which controller ports were used across all inputs //This helps prevent updating controllers that weren't used at the end int[] usedControllerPorts = new int[controllerCount]; ConsoleBase curConsole = InputGlobals.CurrentConsole; //Don't check for overflow to improve performance unchecked { for (int i = 0; i < inputArray.Length; i++) { ref Parser.Input[] inputs = ref inputArray[i]; indices.Clear(); //Press all buttons unless it's a release input for (int j = 0; j < inputs.Length; j++) { indices.Add(j); //Get a reference to avoid copying the struct ref Parser.Input input = ref inputs[j]; //Don't do anything for a wait input if (curConsole.IsWait(input) == true) { continue; } int port = input.controllerPort; //Get the controller we're using IVirtualController controller = vcMngr.GetController(port); //These are set to 1 instead of incremented to prevent any chance of overflow nonWaits[port] = 1; usedControllerPorts[port] = 1; if (input.release == true) { controller.ReleaseInput(input); } else { controller.PressInput(input); } } //Update the controllers if there are non-wait inputs for (int waitIdx = 0; waitIdx < nonWaits.Length; waitIdx++) { if (nonWaits[waitIdx] > 0) { IVirtualController controller = vcMngr.GetController(waitIdx); controller.UpdateController(); nonWaits[waitIdx] = 0; } } sw.Start(); while (indices.Count > 0) { //End the input prematurely if (StopRunningInputs == true) { goto End; } //Release buttons when we should for (int j = indices.Count - 1; j >= 0; j--) { ref Parser.Input input = ref inputs[indices[j]]; if (sw.ElapsedMilliseconds < input.duration) { continue; } //Release if the input isn't a hold and isn't a wait input if (input.hold == false && curConsole.IsWait(input) == false) { int port = input.controllerPort; //Get the controller we're using IVirtualController controller = vcMngr.GetController(port); controller.ReleaseInput(input); //Track that we have a non-wait or hold input so we can update the controller with all input releases at once nonWaits[port] = 1; usedControllerPorts[port] = 1; } indices.RemoveAt(j); } //Update the controllers if there are non-wait inputs for (int waitIdx = 0; waitIdx < nonWaits.Length; waitIdx++) { if (nonWaits[waitIdx] > 0) { IVirtualController controller = vcMngr.GetController(waitIdx); controller.UpdateController(); nonWaits[waitIdx] = 0; } } }