Skip to content

Commit 0d7de83

Browse files
authored
BSNESv115+: Implement snes_controller_latch function, cleanup input polling behavior (#3084)
* BSNESv115+: get rid of input_state + input_poll; just poll * call `snes_controller_latch` on latches done in the core, - this now also actually calls the InputCallbackSystem - needed some edits in the core to support executing the callback even when no controller is connected in port 1 * Fix and somewhat normalize the SnesCallbacks order
1 parent a5a8e85 commit 0d7de83

File tree

19 files changed

+101
-166
lines changed

19 files changed

+101
-166
lines changed

Assets/dll/bsnes.wbx.gz

2.31 KB
Binary file not shown.

src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ public void Dispose()
146146
}
147147

148148
public delegate void snes_video_frame_t(ushort* data, int width, int height, int pitch);
149-
public delegate void snes_input_poll_t();
150-
public delegate short snes_input_state_t(int port, int index, int id);
151-
public delegate void snes_no_lag_t(bool sgb_poll);
152149
public delegate void snes_audio_sample_t(short left, short right);
150+
public delegate short snes_input_poll_t(int port, int index, int id);
151+
public delegate void snes_controller_latch_t();
152+
public delegate void snes_no_lag_t(bool sgb_poll);
153153
public delegate string snes_path_request_t(int slot, string hint, bool required);
154154
public delegate void snes_trace_t(string disassembly, string register_info);
155155
public delegate void snes_read_hook_t(uint address);
@@ -192,11 +192,11 @@ public struct LayerEnables
192192
[StructLayout(LayoutKind.Sequential)]
193193
public class SnesCallbacks
194194
{
195-
public snes_input_poll_t inputPollCb;
196-
public snes_input_state_t inputStateCb;
197-
public snes_no_lag_t noLagCb;
198195
public snes_video_frame_t videoFrameCb;
199196
public snes_audio_sample_t audioSampleCb;
197+
public snes_input_poll_t inputPollCb;
198+
public snes_controller_latch_t controllerLatchCb;
199+
public snes_no_lag_t noLagCb;
200200
public snes_path_request_t pathRequestCb;
201201
public snes_trace_t traceCb;
202202
public snes_read_hook_t readHookCb;

src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesControllers.cs

Lines changed: 49 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -56,32 +56,20 @@ public BsnesControllers(BsnesCore.SnesSyncSettings ss)
5656
Definition.MakeImmutable();
5757
}
5858

59-
public void CoreInputPoll(IController controller)
59+
public short CoreInputPoll(IController controller, int port, int index, int id)
6060
{
61-
// i hope this is correct lol
62-
for (int i = 0; i < 2; i++)
63-
{
64-
_ports[i].UpdateState(_mergers[i].UnMerge(controller));
65-
}
66-
}
67-
68-
public short CoreInputState(int port, int index, int id)
69-
{
70-
return _ports[port].GetState(index, id);
61+
return _ports[port].GetState(_mergers[port].UnMerge(controller), index, id);
7162
}
7263
}
7364

7465
public interface IBsnesController
7566
{
76-
// Updates the internal state; gets called once per frame from the core
77-
void UpdateState(IController controller);
78-
7967
/// <summary>
80-
/// Returns the internal state; gets called potentially many times per frame
68+
/// Corresponds to an InputPoll call from the core; gets called potentially many times per frame
8169
/// </summary>
8270
/// <param name="index">bsnes specific value, sometimes multitap number</param>
8371
/// <param name="id">bsnes specific value, sometimes button number</param>
84-
short GetState(int index, int id);
72+
short GetState(IController controller, int index, int id);
8573

8674
ControllerDefinition Definition { get; }
8775
}
@@ -92,15 +80,11 @@ internal class BsnesUnpluggedController : IBsnesController
9280

9381
public ControllerDefinition Definition => _definition;
9482

95-
public void UpdateState(IController controller) { }
96-
97-
public short GetState(int index, int id) => 0;
83+
public short GetState(IController controller, int index, int id) => 0;
9884
}
9985

10086
internal class BsnesController : IBsnesController
10187
{
102-
private readonly bool[] _state = new bool[12];
103-
10488
private static readonly string[] Buttons =
10589
{
10690
"0Up", "0Down", "0Left", "0Right", "0B", "0A", "0Y", "0X", "0L", "0R", "0Select", "0Start"
@@ -129,67 +113,54 @@ internal class BsnesController : IBsnesController
129113

130114
public ControllerDefinition Definition => _definition;
131115

132-
public void UpdateState(IController controller)
133-
{
134-
for (int i = 0; i < 12; i++)
135-
{
136-
_state[i] = controller.IsPressed(Buttons[i]);
137-
}
138-
}
139-
140-
public short GetState(int index, int id)
116+
public short GetState(IController controller, int index, int id)
141117
{
142118
if (id >= 12)
143119
return 0;
144120

145-
return (short) (_state[id] ? 1 : 0);
121+
return (short) (controller.IsPressed(Buttons[id]) ? 1 : 0);
146122
}
147123
}
148124

149125
internal class BsnesMouseController : IBsnesController
150126
{
151-
private readonly short[] _state = new short[4];
152-
153127
private static readonly ControllerDefinition _definition = new ControllerDefinition("(SNES Controller fragment)")
154128
{ BoolButtons = { "0Mouse Left", "0Mouse Right" } }
155129
.AddXYPair("0Mouse {0}", AxisPairOrientation.RightAndDown, (-127).RangeTo(127), 0); //TODO verify direction against hardware, R+D inferred from behaviour in Mario Paint
156130

157131
public ControllerDefinition Definition => _definition;
158132
public bool LimitAnalogChangeSensitivity { get; init; } = true;
159133

160-
public void UpdateState(IController controller)
134+
public short GetState(IController controller, int index, int id)
161135
{
162-
int x = controller.AxisValue("0Mouse X");
163-
if (LimitAnalogChangeSensitivity)
164-
{
165-
x = x.Clamp(-10, 10);
166-
}
167-
_state[0] = (short) x;
168-
169-
int y = controller.AxisValue("0Mouse Y");
170-
if (LimitAnalogChangeSensitivity)
136+
switch (id)
171137
{
172-
y = y.Clamp(-10, 10);
138+
case 0:
139+
int x = controller.AxisValue("0Mouse X");
140+
if (LimitAnalogChangeSensitivity)
141+
{
142+
x = x.Clamp(-10, 10);
143+
}
144+
return (short) x;
145+
case 1:
146+
int y = controller.AxisValue("0Mouse Y");
147+
if (LimitAnalogChangeSensitivity)
148+
{
149+
y = y.Clamp(-10, 10);
150+
}
151+
return (short) y;
152+
case 2:
153+
return (short) (controller.IsPressed("0Mouse Left") ? 1 : 0);
154+
case 3:
155+
return (short) (controller.IsPressed("0Mouse Right") ? 1 : 0);
156+
default:
157+
return 0;
173158
}
174-
_state[1] = (short) y;
175-
176-
_state[2] = (short) (controller.IsPressed("0Mouse Left") ? 1 : 0);
177-
_state[3] = (short) (controller.IsPressed("0Mouse Right") ? 1 : 0);
178-
}
179-
180-
public short GetState(int index, int id)
181-
{
182-
if (id >= 4)
183-
return 0;
184-
185-
return _state[id];
186159
}
187160
}
188161

189162
internal class BsnesMultitapController : IBsnesController
190163
{
191-
private readonly bool[,] _state = new bool[4, 12];
192-
193164
private static readonly string[] Buttons =
194165
{
195166
"Up", "Down", "Left", "Right", "B", "A", "Y", "X", "L", "R", "Select", "Start"
@@ -221,28 +192,17 @@ internal class BsnesMultitapController : IBsnesController
221192

222193
public ControllerDefinition Definition => _definition;
223194

224-
public void UpdateState(IController controller)
225-
{
226-
for (int port = 0; port < 4; port++)
227-
for (int i = 0; i < 12; i++)
228-
{
229-
_state[port, i] = controller.IsPressed(port + Buttons[i]);
230-
}
231-
}
232-
233-
public short GetState(int index, int id)
195+
public short GetState(IController controller, int index, int id)
234196
{
235197
if (id >= 12 || index >= 4)
236198
return 0;
237199

238-
return (short) (_state[index, id] ? 1 : 0);
200+
return (short) (controller.IsPressed(index + Buttons[id]) ? 1 : 0);
239201
}
240202
}
241203

242204
internal class BsnesPayloadController : IBsnesController
243205
{
244-
private readonly bool[,] _state = new bool[2, 16];
245-
246206
private readonly int[] _buttonsOrder = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3, 12, 13, 14, 15};
247207

248208
private static readonly ControllerDefinition _definition = new("(SNES Controller fragment)")
@@ -252,50 +212,32 @@ internal class BsnesPayloadController : IBsnesController
252212

253213
public ControllerDefinition Definition => _definition;
254214

255-
public void UpdateState(IController controller)
256-
{
257-
for (int index = 0; index < 2; index++)
258-
for (int i = 0; i < 16; i++)
259-
{
260-
_state[index, i] = controller.IsPressed(Definition.BoolButtons[index * 16 + _buttonsOrder[i]]);
261-
}
262-
}
263-
264-
public short GetState(int index, int id)
215+
public short GetState(IController controller, int index, int id)
265216
{
266217
if (index >= 2 || id >= 16)
267218
return 0;
268219

269-
return (short) (_state[index, id] ? 1 : 0);
220+
return (short) (controller.IsPressed(Definition.BoolButtons[index * 16 + _buttonsOrder[id]]) ? 1 : 0);
270221
}
271222
}
272223

273224
internal class BsnesSuperScopeController : IBsnesController
274225
{
275-
private readonly short[] _state = new short[6];
276-
277226
private static readonly ControllerDefinition _definition = new ControllerDefinition("(SNES Controller fragment)")
278227
{ BoolButtons = { "0Trigger", "0Cursor", "0Turbo", "0Pause" } }
279228
.AddLightGun("0Scope {0}");
280229

281230
public ControllerDefinition Definition => _definition;
282231

283-
public void UpdateState(IController controller)
232+
public short GetState(IController controller, int index, int id)
284233
{
285-
_state[0] = (short) controller.AxisValue("0Scope X");
286-
_state[1] = (short) controller.AxisValue("0Scope Y");
287-
for (int i = 0; i < 4; i++)
234+
return id switch
288235
{
289-
_state[i + 2] = (short) (controller.IsPressed(_definition.BoolButtons[i]) ? 1 : 0);
290-
}
291-
}
292-
293-
public short GetState(int index, int id)
294-
{
295-
if (id >= 6)
296-
return 0;
297-
298-
return _state[id];
236+
0 => (short) controller.AxisValue("0Scope X"),
237+
1 => (short) controller.AxisValue("0Scope Y"),
238+
2 or 3 or 4 or 5 => (short) (controller.IsPressed(_definition.BoolButtons[id - 2]) ? 1 : 0),
239+
_ => 0
240+
};
299241
}
300242
}
301243

@@ -311,35 +253,25 @@ public BsnesJustifierController(bool chained)
311253
: new ControllerDefinition("(SNES Controller fragment)")
312254
{BoolButtons = { "0Trigger", "0Start"} }
313255
.AddLightGun("0Justifier {0}");
314-
_state = new short[chained ? 8 : 4];
315256
_chained = chained;
316257
}
317258

318259
private readonly bool _chained;
319-
private readonly short[] _state;
320260

321261
public ControllerDefinition Definition { get; }
322262

323-
public void UpdateState(IController controller)
263+
public short GetState(IController controller, int index, int id)
324264
{
325-
_state[0] = (short) controller.AxisValue("0Justifier X");
326-
_state[1] = (short) controller.AxisValue("0Justifier Y");
327-
_state[2] = (short) (controller.IsPressed(Definition.BoolButtons[0]) ? 1 : 0);
328-
_state[3] = (short) (controller.IsPressed(Definition.BoolButtons[1]) ? 1 : 0);
329-
if (_chained)
330-
{
331-
_state[4] = (short) controller.AxisValue("1Justifier X");
332-
_state[5] = (short) controller.AxisValue("1Justifier Y");
333-
_state[6] = (short) (controller.IsPressed(Definition.BoolButtons[2]) ? 1 : 0);
334-
_state[7] = (short) (controller.IsPressed(Definition.BoolButtons[3]) ? 1 : 0);
335-
}
336-
}
337-
public short GetState(int index, int id)
338-
{
339-
if (index >= 2 || id >= 4 || (index == 1 && !_chained))
265+
if (index == 1 && !_chained)
340266
return 0;
341267

342-
return _state[index * 4 + id];
268+
return id switch
269+
{
270+
0 => (short) controller.AxisValue($"{index}Justifier X"),
271+
1 => (short) controller.AxisValue($"{index}Justifier Y"),
272+
2 or 3 => (short) (controller.IsPressed(Definition.BoolButtons[index * 2 + id]) ? 1 : 0),
273+
_ => 0
274+
};
343275
}
344276
}
345277
}

src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ public BsnesCore(GameInfo game, byte[] romData, byte[] xmlData, string baseRomPa
5454
BsnesApi.SnesCallbacks callbacks = new()
5555
{
5656
inputPollCb = snes_input_poll,
57-
inputStateCb = snes_input_state,
5857
noLagCb = snes_no_lag,
58+
controllerLatchCb = snes_controller_latch,
5959
videoFrameCb = snes_video_refresh,
6060
audioSampleCb = snes_audio_sample,
6161
pathRequestCb = snes_path_request,
@@ -283,30 +283,29 @@ private void LoadCurrent()
283283
_region = Api.core.snes_get_region();
284284
}
285285

286-
// poll which updates the controller state
287-
private void snes_input_poll()
288-
{
289-
_controllers.CoreInputPoll(_controller);
290-
}
291-
292286
/// <param name="port">0 or 1, corresponding to L and R physical ports on the snes</param>
293287
/// <param name="index">meaningless for most controllers. for multitap, 0-3 for which multitap controller</param>
294288
/// <param name="id">button ID enum; in the case of a regular controller, this corresponds to shift register position</param>
295289
/// <returns>for regular controllers, one bit D0 of button status. for other controls, varying ranges depending on id</returns>
296-
private short snes_input_state(int port, int index, int id)
290+
private short snes_input_poll(int port, int index, int id)
297291
{
298-
return _controllers.CoreInputState(port, index, id);
292+
return _controllers.CoreInputPoll(_controller, port, index, id);
299293
}
300294

301295
private void snes_no_lag(bool sgbPoll)
302296
{
303-
// gets called whenever there was input polled, aka no lag
297+
// gets called whenever there was input read in the core
304298
if (!IsSGB || sgbPoll)
305299
{
306300
IsLagFrame = false;
307301
}
308302
}
309303

304+
private void snes_controller_latch()
305+
{
306+
InputCallbacks.Call();
307+
}
308+
310309
private readonly int[] palette = new int[0x8000];
311310

312311
private void generate_palette()

src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
1414

1515
int n = Core.gpgx_getregs(regs);
1616
if (n > regs.Length)
17-
throw new InvalidOperationException("A buffer overrun has occured!");
17+
throw new InvalidOperationException("A buffer overrun has occured!");
1818
var ret = new Dictionary<string, RegisterValue>();
1919
using (_elf.EnterExit())
2020
{

waterbox/bsnescore/bsnes/sfc/controller/controller.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ Controller::Controller(uint port) : port(port) {
1616
Controller::~Controller() {
1717
}
1818

19+
auto Controller::latch(bool data) -> void {
20+
if(latched == data) return;
21+
latched = data;
22+
23+
if (latched == 0 && port == ID::Port::Controller1) platform->notify("LATCH");
24+
}
25+
1926
auto Controller::iobit() -> bool {
2027
switch(port) {
2128
case ID::Port::Controller1: return cpu.pio() & 0x40;

waterbox/bsnescore/bsnes/sfc/controller/controller.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ struct Controller {
1818
auto iobit() -> bool;
1919
auto iobit(bool data) -> void;
2020
virtual auto data() -> uint2 { return 0; }
21-
virtual auto latch(bool data) -> void {}
21+
virtual auto latch(bool data) -> void;
2222
virtual auto latch() -> void {} //light guns
2323
virtual auto draw(uint16_t* output, uint pitch, uint width, uint height) -> void {} //light guns
2424

2525
const uint port;
26+
27+
protected:
28+
bool latched;
2629
};
2730

2831
struct ControllerPort {

0 commit comments

Comments
 (0)