Handmade Hero»Forums»Code
152 posts / 1 project
I am finalspace and do programming since more than 25 years, started on C64 and got serious with borland delphi. Nowadays i use C/C++ only.
[Solved] Trouble with the handmade hero input system (HalfTransitionCount issue)
Edited by Finalspace on
I am having trouble getting WasDown() working for my own project which uses the same input concept like handmade-hero.
The key presses are properly detected, but the half-transition count is zero in most cases - even in situations while holding down the button. Weirdly enough sometimes it gets to a state where the half-transition count is one and the ended-down state is false, meaning that it detects the button was down in some cases, but it does not work reliably :-(

This results in me hitting the space bar over and over again, just to start the game or launch the ball (I am making a breakout game).

I am logging out the every WasPressed detection for my desired key and the result is just weird:

The first 3-6 tries:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
...
// Started holding down space-bar
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 1 / 0 = false
// Released space-bar
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
...


One try which actually worked:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
...
// Started holding down space-bar
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
// Released space-bar
Was pressed[KB-ActionDown]: 1 / 1 = true
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
...


As you can see in the second log, the halfTransitionCount is 1 and endedDown is true, but in the first log the half-transition just stays zero.

Also i tested if my platform layer handles the keyboard events correctly and it does:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// Space-Bar hit
Frame 43, Key button change[32] = 1
// Space-Bar release
Frame 48, Key button change[32] = 0

// Space-Bar hit
Frame 73, Key button change[32] = 1
// Space-Bar release
Frame 78, Key button change[32] = 0

// Space-Bar hit
Frame 146 Key button change[32] = 1
// Space-Bar release
Frame 150 Key button change[32] = 0

// Space-Bar holding-down
Frame 166, Key button change[32] = 1
// Space-Bar repeat detected (Not used)
Frame 196, Key button press[32] = 2
Frame 198, Key button press[32] = 2
Frame 200, Key button press[32] = 2
Frame 202, Key button press[32] = 2
Frame 204, Key button press[32] = 2
Frame 206, Key button press[32] = 2
Frame 208, Key button press[32] = 2
Frame 210, Key button press[32] = 2
Frame 212, Key button press[32] = 2
Frame 214, Key button press[32] = 2
Frame 216, Key button press[32] = 2
Frame 218, Key button press[32] = 2
Frame 220, Key button press[32] = 2
Frame 222, Key button press[32] = 2
Frame 224, Key button press[32] = 2
Frame 226, Key button press[32] = 2
Frame 228, Key button press[32] = 2
Frame 230, Key button press[32] = 2
Frame 232, Key button press[32] = 2
Frame 234, Key button press[32] = 2
Frame 236, Key button press[32] = 2
Frame 238, Key button press[32] = 2
Frame 240, Key button press[32] = 2
Frame 242, Key button press[32] = 2
Frame 244, Key button press[32] = 2
Frame 246, Key button press[32] = 2
Frame 248, Key button press[32] = 2
Frame 250, Key button press[32] = 2
Frame 252, Key button press[32] = 2
Frame 254, Key button press[32] = 2
Frame 256, Key button press[32] = 2
Frame 258, Key button press[32] = 2
Frame 260, Key button press[32] = 2
Frame 262, Key button press[32] = 2
Frame 264, Key button press[32] = 2
Frame 266, Key button press[32] = 2
Frame 268, Key button press[32] = 2
Frame 270, Key button press[32] = 2
Frame 271, Key button press[32] = 2
Frame 273, Key button press[32] = 2
Frame 275, Key button press[32] = 2
Frame 277, Key button press[32] = 2
Frame 279, Key button press[32] = 2
Frame 281, Key button press[32] = 2
Frame 283, Key button press[32] = 2
Frame 285, Key button press[32] = 2
Frame 287, Key button press[32] = 2
Frame 289, Key button press[32] = 2
Frame 291, Key button press[32] = 2
Frame 293, Key button press[32] = 2
Frame 295, Key button press[32] = 2
Frame 297, Key button press[32] = 2
// Space-Bar release
Frame 298, Key button change[32] = 0


I have this bug since months and still havent figured out what is wrong, so i would appreciate any kind of help.

Here is actual source:

Platform layer:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
struct ButtonState {
	const char *name;
	int halfTransitionCount;
	fpl_b32 endedDown;
};

inline bool WasPressed(const ButtonState &state) {
	bool result = ((state.halfTransitionCount > 1) || ((state.halfTransitionCount == 1) && (state.endedDown)));
	if(state.name != nullptr) {
		fplDebugFormatOut("Was pressed[%s]: %d / %d = %s\n", state.name, state.halfTransitionCount, state.endedDown, (result ? "true" : "false"));
	}
	return(result);
}
inline bool IsDown(const ButtonState &state) {
	bool result = state.endedDown != 0;
	return(result);
}

struct Controller {
	bool isConnected;
	bool isAnalog;
	Vec2f analogMovement;
	union {
		struct {
			ButtonState moveUp;
			ButtonState moveDown;
			ButtonState moveLeft;
			ButtonState moveRight;
			ButtonState actionUp;
			ButtonState actionDown;
			ButtonState actionLeft;
			ButtonState actionRight;
			ButtonState actionBack;
			ButtonState actionStart;
			ButtonState debugToggle;
			ButtonState debugReload;
		};
		ButtonState buttons[12];
	};
};

struct Mouse {
	Vec2i pos;
	float wheelDelta;
	union {
		struct {
			ButtonState left;
			ButtonState middle;
			ButtonState right;
		};
		ButtonState buttons[3];
	};
};

struct Input {
	float deltaTime;
	float framesPerSeconds;
	union {
		struct {
			Controller keyboard;
			Controller gamepad[4];
		};
		Controller controllers[5];
	};
	Mouse mouse;
	Vec2i windowSize;
	int defaultControllerIndex;
	bool isActive;
};

static void UpdateKeyboardButtonState(ButtonState &newState, const fpl_b32 isDown) {
	FPL_ASSERT(newState.endedDown != isDown);
	newState.endedDown = isDown;
	++newState.halfTransitionCount;
}

static void UpdateDigitalButtonState(const ButtonState &oldState, ButtonState &newState, const fpl_b32 isDown) {
	newState.endedDown = isDown;
	newState.halfTransitionCount = ((newState.endedDown == oldState.endedDown) ? 0 : 1);
}

static void UpdateDefaultController(Input *currentInput, int newIndex) {
	if(newIndex != -1) {
		currentInput->defaultControllerIndex = newIndex;
	} else {
		currentInput->defaultControllerIndex = -1;
		for(int i = FPL_ARRAYCOUNT(currentInput->controllers) - 1; i > 0; i--) {
			if(currentInput->controllers[i].isConnected) {
				currentInput->defaultControllerIndex = i;
				break;
			}
		}
	}
}

static void ProcessEvents(Input *currentInput, Input *prevInput, GameWindowActiveType &windowActiveType, Vec2i &lastMousePos) {
	Controller *newKeyboardController = &currentInput->keyboard;
	fplEvent event;
	while(fplPollEvent(&event)) {
		switch(event.type) {
			case fplEventType_Window:
			{
				switch(event.window.type) {
					case fplWindowEventType_GotFocus:
						windowActiveType = GameWindowActiveType::GotFocus;
						break;
					case fplWindowEventType_Restored:
						windowActiveType = GameWindowActiveType::Restored;
						break;
					case fplWindowEventType_Maximized:
						windowActiveType = GameWindowActiveType::Maximized;
						break;
					case fplWindowEventType_LostFocus:
						windowActiveType = GameWindowActiveType::LostFocus;
						break;
					case fplWindowEventType_Minimized:
						windowActiveType = GameWindowActiveType::Minimized;
						break;
				}
			} break;

			case fplEventType_Gamepad:
			{
				// @TODO(final): For now we just use the device index, but later it should be "added" to the controllers array and remembered somehow
				uint32_t controllerIndex = 1 + event.gamepad.deviceIndex;
				FPL_ASSERT(controllerIndex < FPL_ARRAYCOUNT(currentInput->controllers));
				Controller *newController = &currentInput->controllers[controllerIndex];
				Controller *oldController = &prevInput->controllers[controllerIndex];
				switch(event.gamepad.type) {
					case fplGamepadEventType_Connected:
					{
						newController->isConnected = true;
						UpdateDefaultController(currentInput, controllerIndex);
					} break;
					case fplGamepadEventType_Disconnected:
					{
						newController->isConnected = false;
						UpdateDefaultController(currentInput, -1);
					} break;
					case fplGamepadEventType_StateChanged:
					{
						fplGamepadState &padstate = event.gamepad.state;
						assert(newController->isConnected);
						if(Abs(padstate.leftStickX) > 0.0f || Abs(padstate.leftStickY) > 0.0f) {
							newController->isAnalog = true;
							newController->analogMovement.x = padstate.leftStickX;
							newController->analogMovement.y = padstate.leftStickY;
						} else {
							newController->isAnalog = false;
							UpdateDigitalButtonState(oldController->moveDown, newController->moveDown, padstate.dpadDown.isDown != 0);
							UpdateDigitalButtonState(oldController->moveUp, newController->moveUp, padstate.dpadUp.isDown != 0);
							UpdateDigitalButtonState(oldController->moveLeft, newController->moveLeft, padstate.dpadLeft.isDown != 0);
							UpdateDigitalButtonState(oldController->moveRight, newController->moveRight, padstate.dpadRight.isDown != 0);
						}
						UpdateDigitalButtonState(oldController->actionDown, newController->actionDown, padstate.actionA.isDown != 0);
						UpdateDigitalButtonState(oldController->actionRight, newController->actionRight, padstate.actionB.isDown != 0);
						UpdateDigitalButtonState(oldController->actionLeft, newController->actionLeft, padstate.actionX.isDown != 0);
						UpdateDigitalButtonState(oldController->actionUp, newController->actionUp, padstate.actionY.isDown != 0);
						UpdateDigitalButtonState(oldController->actionBack, newController->actionBack, padstate.back.isDown != 0);
						UpdateDigitalButtonState(oldController->actionStart, newController->actionStart, padstate.start.isDown != 0);
					} break;
				}
			} break;

			case fplEventType_Mouse:
			{
				switch(event.mouse.type) {
					case fplMouseEventType_Move:
					{
						currentInput->mouse.pos = lastMousePos = V2i(event.mouse.mouseX, event.mouse.mouseY);
					} break;

					case fplMouseEventType_Button:
					{
						bool isDown = event.mouse.buttonState >= fplButtonState_Press;
						if(event.mouse.mouseButton == fplMouseButtonType_Left) {
							UpdateKeyboardButtonState(currentInput->mouse.left, isDown);
						} else if(event.mouse.mouseButton == fplMouseButtonType_Right) {
							UpdateKeyboardButtonState(currentInput->mouse.right, isDown);
						} else if(event.mouse.mouseButton == fplMouseButtonType_Middle) {
							UpdateKeyboardButtonState(currentInput->mouse.middle, isDown);
						}
					} break;

					case fplMouseEventType_Wheel:
					{
						currentInput->mouse.wheelDelta = event.mouse.wheelDelta;
					} break;
				}
			} break;

			case fplEventType_Keyboard:
			{
				switch(event.keyboard.type) {
					case fplKeyboardEventType_Input:
					{
					} break;
					case fplKeyboardEventType_Button:
					{
						if(!newKeyboardController->isConnected) {
							newKeyboardController->isConnected = true;
							if(currentInput->defaultControllerIndex == -1) {
								UpdateDefaultController(currentInput, 0);
							}
						}
						bool isDown = event.keyboard.buttonState >= fplButtonState_Press;
						bool wasDown = event.keyboard.buttonState == fplButtonState_Release || event.keyboard.buttonState == fplButtonState_Repeat;
						if(isDown != wasDown) {
							fplDebugFormatOut("Frame %d, Key button change[%d] = %d\n", currentInput->frameIndex, event.keyboard.keyCode, event.keyboard.buttonState);
							switch(event.keyboard.mappedKey) {
								case fplKey_A:
								case fplKey_Left:
									UpdateKeyboardButtonState(newKeyboardController->moveLeft, isDown);
									break;
								case fplKey_D:
								case fplKey_Right:
									UpdateKeyboardButtonState(newKeyboardController->moveRight, isDown);
									break;
								case fplKey_W:
								case fplKey_Up:
									UpdateKeyboardButtonState(newKeyboardController->moveUp, isDown);
									break;
								case fplKey_S:
								case fplKey_Down:
									UpdateKeyboardButtonState(newKeyboardController->moveDown, isDown);
									break;
								case fplKey_Space:
									UpdateKeyboardButtonState(newKeyboardController->actionDown, isDown);
									break;
								case fplKey_F4:
									UpdateKeyboardButtonState(newKeyboardController->debugToggle, isDown);
									break;
								case fplKey_R:
									UpdateKeyboardButtonState(newKeyboardController->debugReload, isDown);
									break;
								case fplKey_Return:
									UpdateKeyboardButtonState(newKeyboardController->actionStart, isDown);
									break;
								case fplKey_Escape:
									UpdateKeyboardButtonState(newKeyboardController->actionBack, isDown);
									break;
							}
						} else {
							fplDebugFormatOut("Frame %d, Key button press[%d] = %d\n", currentInput->frameIndex, event.keyboard.keyCode, event.keyboard.buttonState);
						}
						if(wasDown) {
							if(event.keyboard.mappedKey == fplKey_F) {
								bool wasFullscreen = fplIsWindowFullscreen();
								fplSetWindowFullscreen(!wasFullscreen, 0, 0, 0);
							}
						}
					} break;
				}
			} break;
		}
	}
}

static uint32_t GameAudioPlayback(const fplAudioDeviceFormat *outFormat, const uint32_t frameCount, void *outputSamples, void *userData) {
	AudioSystem *audioSys = (AudioSystem *)userData;
	uint32_t result = AudioSystemWriteSamples(audioSys, outFormat, frameCount, (uint8_t *)outputSamples);
	return(result);
}

extern int GameMain(const GameConfiguration &config) {
	fplSettings settings = fplMakeDefaultSettings();
	settings.video.driver = fplVideoDriverType_OpenGL;
	settings.video.graphics.opengl.compabilityFlags = fplOpenGLCompabilityFlags_Legacy;
	settings.video.isVSync = true;
	fplCopyAnsiString(config.title, settings.window.windowTitle, FPL_ARRAYCOUNT(settings.window.windowTitle));

	if(!fplPlatformInit(fplInitFlags_All, &settings)) {
		return -1;
	}

	if(!fglLoadOpenGL(true)) {
		fplPlatformRelease();
		return -1;
	}

	bool wasError = false;

	fmemMemoryBlock gameMemoryBlock = {};
	if(!fmemInit(&gameMemoryBlock, fmemType_Growable, FMEM_MEGABYTES(128))) {
		wasError = true;
	}
	fmemMemoryBlock renderMemoryBlock = {};
	if(!fmemInit(&renderMemoryBlock, fmemType_Growable, FMEM_MEGABYTES(32))) {
		wasError = true;
	}

	AudioSystem audioSys = {};
	if(!AudioSystemInit(&audioSys)) {
		wasError = true;
	}

	fplSetAudioClientReadCallback(GameAudioPlayback, &audioSys);
	if(fplPlayAudio() != fplAudioResult_Success) {
		wasError = true;
	}

	RenderState renderState = {};
	InitRenderState(renderState, renderMemoryBlock);
	InitOpenGLRenderer();

	GameMemory gameMem = {};
	gameMem.memory = &gameMemoryBlock;
	gameMem.render = &renderState;
	if(!GameInit(gameMem)) {
		wasError = true;
	}

	if(!wasError) {
		const double TargetDeltaTime = 1.0 / 60.0;

		if(config.hideMouseCursor) {
			fplSetWindowCursorEnabled(false);
		}

		Input inputs[2] = {};
		Input *newInput = &inputs[0];
		Input *oldInput = &inputs[1];
		Vec2i lastMousePos = V2i(-1, -1);
		GameWindowActiveType windowActiveType[2] = { GameWindowActiveType::None, GameWindowActiveType::None };
		newInput->defaultControllerIndex = -1;

		uint32_t frameCount = 0;
		uint32_t updateCount = 0;
		double lastTime = fplGetTimeInSecondsHP();
		double fpsTimerInSecs = fplGetTimeInSecondsHP();
		double frameAccumulator = TargetDeltaTime;
		double lastFramesPerSecond = 0.0;
		double lastFrameTime = 0.0;
		int frameIndex = 0;

		while(!IsGameExiting(gameMem) && fplWindowUpdate()) {
			// Window size
			fplWindowSize winArea;
			if(fplGetWindowArea(&winArea)) {
				newInput->windowSize.x = winArea.width;
				newInput->windowSize.y = winArea.height;
			}

			// Remember previous keyboard and mouse state
			windowActiveType[1] = windowActiveType[0];
			Controller *oldKeyboardController = &oldInput->keyboard;
			Controller *newKeyboardController = &newInput->keyboard;
			*newKeyboardController = {};
			newKeyboardController->isConnected = oldKeyboardController->isConnected;
			newKeyboardController->actionDown.name = "KB-ActionDown";

			Mouse *newMouse = &newInput->mouse;
			Mouse *oldMouse = &oldInput->mouse;
			*newMouse = {};
			for(uint32_t buttonIndex = 0; buttonIndex < FPL_ARRAYCOUNT(newMouse->buttons); ++buttonIndex) {
				newMouse->buttons[buttonIndex] = oldMouse->buttons[buttonIndex];
				newMouse->buttons[buttonIndex].halfTransitionCount = 0;
			}
			newMouse->pos = lastMousePos;

			// Remember previous gamepad connected states
			for(uint32_t controllerIndex = 1; controllerIndex < FPL_ARRAYCOUNT(newInput->controllers); ++controllerIndex) {
				Controller *newGamepadController = &newInput->controllers[controllerIndex];
				Controller *oldGamepadController = &oldInput->controllers[controllerIndex];
				newGamepadController->isConnected = oldGamepadController->isConnected;
				newGamepadController->isAnalog = oldGamepadController->isAnalog;
			}

			for(uint32_t buttonIndex = 0; buttonIndex < FPL_ARRAYCOUNT(newKeyboardController->buttons); ++buttonIndex) {
				newKeyboardController->buttons[buttonIndex].endedDown = oldKeyboardController->buttons[buttonIndex].endedDown;
			}

			newInput->deltaTime = (float)TargetDeltaTime;
			newInput->framesPerSeconds = (float)lastFramesPerSecond;
			newInput->frameIndex = frameIndex++;

			// Events
			ProcessEvents(newInput, oldInput, windowActiveType[0], lastMousePos);
			if(config.disableInactiveDetection) {
				newInput->isActive = (windowActiveType[0] & GameWindowActiveType::Minimized) != GameWindowActiveType::Minimized;
			} else {
				newInput->isActive = ((windowActiveType[0] & GameWindowActiveType::Minimized) != GameWindowActiveType::Minimized) && ((windowActiveType[0] & GameWindowActiveType::LostFocus) != GameWindowActiveType::LostFocus);
			}

			if(windowActiveType[0] != windowActiveType[1]) {
				// We dont want to have delta time jumps
				lastTime = fplGetTimeInSecondsHP();
				lastFramesPerSecond = 0.0f;
				fpsTimerInSecs = fplGetTimeInSecondsHP();
				updateCount = frameCount = 0;
				frameAccumulator = TargetDeltaTime;
			}

			ResetRenderState(renderState);

			// Game Update
			if(config.noUpdateRenderSeparation) {
				float alpha;
				if(lastFrameTime > 0) {
					alpha = 1.0f - (float)(lastFrameTime / TargetDeltaTime);
				} else {
					alpha = 1.0f;
				}
				GameUpdateAndRender(gameMem, *newInput, alpha);
			} else {
				GameInput(gameMem, *newInput);
				while(frameAccumulator >= TargetDeltaTime) {
					GameUpdate(gameMem, *newInput);
					frameAccumulator -= TargetDeltaTime;
					++updateCount;
				}
			}

			// @TODO(final): Yield thread when we are running too fast
			double endWorkTime = fplGetTimeInSecondsHP();
			double workDuration = endWorkTime - lastTime;

			// Render
			if(!config.noUpdateRenderSeparation) {
				float alpha = (float)frameAccumulator / (float)TargetDeltaTime;
				GameRender(gameMem, alpha);
			}
			RenderWithOpenGL(renderState);
			fplVideoFlip();
			++frameCount;

			// Timing
			double endTime = fplGetTimeInSecondsHP();
			double frameDuration = endTime - lastTime;
			lastFrameTime = frameDuration;
			lastFramesPerSecond = 1.0f / frameDuration;
			if(!config.noUpdateRenderSeparation) {
				frameAccumulator += frameDuration;
				frameAccumulator = FPL_MIN(0.1, frameAccumulator);
			}
			lastTime = endTime;
			if(endTime >= (fpsTimerInSecs + 1.0)) {
				fpsTimerInSecs = endTime;
#if 0
				char charBuffer[256];
				fplFormatAnsiString(charBuffer, FPL_ARRAYCOUNT(charBuffer), "Fps: %d, Ups: %d\n", frameCount, updateCount);
				OutputDebugStringA(charBuffer);
#endif
				frameCount = 0;
				updateCount = 0;
			}

			// Swap input
			{
				Input *tmp = newInput;
				newInput = oldInput;
				oldInput = tmp;
			}
		}

		if(config.hideMouseCursor) {
			fplSetWindowCursorEnabled(true);
		}

		GameRelease(gameMem);
	}

	fplStopAudio();

	AudioSystemShutdown(&audioSys);

	fmemFree(&gameMemoryBlock);
	fmemFree(&renderMemoryBlock);

	fglUnloadOpenGL();

	fplPlatformRelease();

	int result = wasError ? -1 : 0;
	return (result);
}


// Game code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
extern void GameInput(GameMemory &gameMemory, const Input &input) {
	if(!input.isActive) {
		return;
	}

	GameState *state = gameMemory.game;
	FPL_ASSERT(state != nullptr);

        // As soon as any keyboard button press is detected, the defaultControllerIndex is set to 0 (Keyboard-Controller).
        // If a gamepad is plugged in the defaultControllerIndex is greater than 0 always.

	if(input.defaultControllerIndex != -1) {
		FPL_ASSERT(input.defaultControllerIndex < FPL_ARRAYCOUNT(input.controllers));
		const Controller *controller = &input.controllers[input.defaultControllerIndex];
		if(controller->isConnected) {
			switch(state->mode) {
				case GameMode::Play:
				{
					// Single player input
					Paddle &paddle = state->paddle.paddle;
					if(IsDown(controller->moveLeft)) {
						paddle.body->ApplyLinearImpulse(paddle.speed * b2Vec2(-1, 0), paddle.body->GetPosition(), true);
					} else if(IsDown(controller->moveRight)) {
						paddle.body->ApplyLinearImpulse(paddle.speed * b2Vec2(1, 0), paddle.body->GetPosition(), true);
					}
					if(WasPressed(controller->actionDown) && paddle.gluedBall != nullptr) {
						LaunchBall(*state);
					}
				} break;

			}
		}
	}
}
Simon Anciaux
1337 posts
[Solved] Trouble with the handmade hero input system (HalfTransitionCount issue)
Edited by Simon Anciaux on Reason: More info
Could you provide a sample that we could compile and debug ?

I tried your code and it works all the time for me. (I'm on Windows 7 64bit).
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
Frame 224, Key button change[32] = 1
Was pressed[KB-ActionDown]: 1 / 1 = true
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 234, Key button change[32] = 0
Was pressed[KB-ActionDown]: 1 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
...
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
Frame 308, Key button change[32] = 1
Was pressed[KB-ActionDown]: 1 / 1 = true
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 338, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 340, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 342, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 344, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 346, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 348, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 350, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 351, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 353, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 355, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 357, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 359, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 361, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 363, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 365, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 367, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 369, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 371, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 373, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 375, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 377, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 379, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 381, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 383, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 385, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 387, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 389, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 390, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 392, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 394, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 396, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 398, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 400, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 402, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 404, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 406, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 408, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 410, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 412, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 414, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 416, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 418, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 420, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 422, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 424, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 426, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 428, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 429, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 431, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 433, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 435, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 437, Key button press[32] = 2
Was pressed[KB-ActionDown]: 0 / 1 = false
Frame 438, Key button change[32] = 0
Was pressed[KB-ActionDown]: 1 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false
Was pressed[KB-ActionDown]: 0 / 0 = false

Here is the code I used, with fpl from github. Does that work on your machine ? I only added missing structs and functions and removed part of the code that relied on your game or on fpl functions not present in the github version.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
// cl Y:\fpl\main.cpp -Fefpl.exe -nologo -Gm- -GR- -EHa- -Oi -fp:fast -WX -W4 -wd4100 -wd4189 -wd4201 -wd4204 -wd4505 -wd4996 -FC -Fm -Zi -Od -MTd -link -INCREMENTAL:NO -opt:ref -subsystem:windows -entry:mainCRTStartup user32.lib OpenGL32.lib GDI32.lib

#include <stdint.h>

#define FPL_IMPLEMENTATION
#include "final_platform_layer.h"

typedef uint32_t fpl_b32;
struct Vec2f {
    float x, y;
};
struct Vec2i {
    int x, y;
};

Vec2i V2i( int x, int y ) {
    Vec2i result = { x, y };
    return result;
}

struct ButtonState {
	const char *name;
	int halfTransitionCount;
	fpl_b32 endedDown;
};

inline bool WasPressed(const ButtonState &state) {
	bool result = ((state.halfTransitionCount > 1) || ((state.halfTransitionCount == 1) && (state.endedDown)));
	if(state.name != nullptr) {
		fplDebugFormatOut("Was pressed[%s]: %d / %d = %s\n", state.name, state.halfTransitionCount, state.endedDown, (result ? "true" : "false"));
	}
	return(result);
}
inline bool IsDown(const ButtonState &state) {
	bool result = state.endedDown != 0;
	return(result);
}

struct Controller {
	bool isConnected;
	bool isAnalog;
	Vec2f analogMovement;
	union {
		struct {
			ButtonState moveUp;
			ButtonState moveDown;
			ButtonState moveLeft;
			ButtonState moveRight;
			ButtonState actionUp;
			ButtonState actionDown;
			ButtonState actionLeft;
			ButtonState actionRight;
			ButtonState actionBack;
			ButtonState actionStart;
			ButtonState debugToggle;
			ButtonState debugReload;
		};
		ButtonState buttons[12];
	};
};

struct Mouse {
	Vec2i pos;
	float wheelDelta;
	union {
		struct {
			ButtonState left;
			ButtonState middle;
			ButtonState right;
		};
		ButtonState buttons[3];
	};
};

struct Input {
	float deltaTime;
	float framesPerSeconds;
	union {
		struct {
			Controller keyboard;
			Controller gamepad[4];
		};
		Controller controllers[5];
	};
	Mouse mouse;
	Vec2i windowSize;
	int defaultControllerIndex;
	bool isActive;
    int frameIndex;
};

static void UpdateKeyboardButtonState(ButtonState &newState, const fpl_b32 isDown) {
	FPL_ASSERT(newState.endedDown != isDown);
	newState.endedDown = isDown;
	++newState.halfTransitionCount;
}

static void UpdateDigitalButtonState(const ButtonState &oldState, ButtonState &newState, const fpl_b32 isDown) {
	newState.endedDown = isDown;
	newState.halfTransitionCount = ((newState.endedDown == oldState.endedDown) ? 0 : 1);
}

static void UpdateDefaultController(Input *currentInput, int newIndex) {
	if(newIndex != -1) {
		currentInput->defaultControllerIndex = newIndex;
	} else {
		currentInput->defaultControllerIndex = -1;
		for(int i = FPL_ARRAYCOUNT(currentInput->controllers) - 1; i > 0; i--) {
			if(currentInput->controllers[i].isConnected) {
				currentInput->defaultControllerIndex = i;
				break;
			}
		}
	}
}

static void ProcessEvents(Input *currentInput, Input *prevInput, Vec2i &lastMousePos) {
	Controller *newKeyboardController = &currentInput->keyboard;
	fplEvent event;
	while(fplPollEvent(&event)) {
		switch(event.type) {
			case fplEventType_Window:
			{
#if 0
				switch(event.window.type) {
					case fplWindowEventType_GotFocus:
                    windowActiveType = GameWindowActiveType::GotFocus;
                    break;
					case fplWindowEventType_Restored:
                    windowActiveType = GameWindowActiveType::Restored;
                    break;
					case fplWindowEventType_Maximized:
                    windowActiveType = GameWindowActiveType::Maximized;
                    break;
					case fplWindowEventType_LostFocus:
                    windowActiveType = GameWindowActiveType::LostFocus;
                    break;
					case fplWindowEventType_Minimized:
                    windowActiveType = GameWindowActiveType::Minimized;
                    break;
				}
#endif
			} break;
            
			case fplEventType_Gamepad:
			{
#if 0
				// @TODO(final): For now we just use the device index, but later it should be "added" to the controllers array and remembered somehow
				uint32_t controllerIndex = 1 + event.gamepad.deviceIndex;
				FPL_ASSERT(controllerIndex < FPL_ARRAYCOUNT(currentInput->controllers));
				Controller *newController = &currentInput->controllers[controllerIndex];
				Controller *oldController = &prevInput->controllers[controllerIndex];
				switch(event.gamepad.type) {
					case fplGamepadEventType_Connected:
					{
						newController->isConnected = true;
						UpdateDefaultController(currentInput, controllerIndex);
					} break;
					case fplGamepadEventType_Disconnected:
					{
						newController->isConnected = false;
						UpdateDefaultController(currentInput, -1);
					} break;
					case fplGamepadEventType_StateChanged:
					{
						fplGamepadState &padstate = event.gamepad.state;
						assert(newController->isConnected);
						if(Abs(padstate.leftStickX) > 0.0f || Abs(padstate.leftStickY) > 0.0f) {
							newController->isAnalog = true;
							newController->analogMovement.x = padstate.leftStickX;
							newController->analogMovement.y = padstate.leftStickY;
						} else {
							newController->isAnalog = false;
							UpdateDigitalButtonState(oldController->moveDown, newController->moveDown, padstate.dpadDown.isDown != 0);
							UpdateDigitalButtonState(oldController->moveUp, newController->moveUp, padstate.dpadUp.isDown != 0);
							UpdateDigitalButtonState(oldController->moveLeft, newController->moveLeft, padstate.dpadLeft.isDown != 0);
							UpdateDigitalButtonState(oldController->moveRight, newController->moveRight, padstate.dpadRight.isDown != 0);
						}
						UpdateDigitalButtonState(oldController->actionDown, newController->actionDown, padstate.actionA.isDown != 0);
						UpdateDigitalButtonState(oldController->actionRight, newController->actionRight, padstate.actionB.isDown != 0);
						UpdateDigitalButtonState(oldController->actionLeft, newController->actionLeft, padstate.actionX.isDown != 0);
						UpdateDigitalButtonState(oldController->actionUp, newController->actionUp, padstate.actionY.isDown != 0);
						UpdateDigitalButtonState(oldController->actionBack, newController->actionBack, padstate.back.isDown != 0);
						UpdateDigitalButtonState(oldController->actionStart, newController->actionStart, padstate.start.isDown != 0);
					} break;
				}
#endif
			} break;
            
			case fplEventType_Mouse:
			{
				switch(event.mouse.type) {
					case fplMouseEventType_Move:
					{
						currentInput->mouse.pos = lastMousePos = V2i(event.mouse.mouseX, event.mouse.mouseY);
					} break;
                    
					case fplMouseEventType_Button:
					{
						bool isDown = event.mouse.buttonState >= fplButtonState_Press;
						if(event.mouse.mouseButton == fplMouseButtonType_Left) {
							UpdateKeyboardButtonState(currentInput->mouse.left, isDown);
						} else if(event.mouse.mouseButton == fplMouseButtonType_Right) {
							UpdateKeyboardButtonState(currentInput->mouse.right, isDown);
						} else if(event.mouse.mouseButton == fplMouseButtonType_Middle) {
							UpdateKeyboardButtonState(currentInput->mouse.middle, isDown);
						}
					} break;
                    
					case fplMouseEventType_Wheel:
					{
						currentInput->mouse.wheelDelta = event.mouse.wheelDelta;
					} break;
				}
			} break;
            
			case fplEventType_Keyboard:
			{
				switch(event.keyboard.type) {
					case fplKeyboardEventType_Input:
					{
					} break;
					case fplKeyboardEventType_Button:
					{
						if(!newKeyboardController->isConnected) {
							newKeyboardController->isConnected = true;
							if(currentInput->defaultControllerIndex == -1) {
								UpdateDefaultController(currentInput, 0);
							}
						}
						bool isDown = event.keyboard.buttonState >= fplButtonState_Press;
						bool wasDown = event.keyboard.buttonState == fplButtonState_Release || event.keyboard.buttonState == fplButtonState_Repeat;
						if(isDown != wasDown) {
                            
							fplDebugFormatOut("Frame %d, Key button change[%d] = %d\n", currentInput->frameIndex, event.keyboard.keyCode, event.keyboard.buttonState);
							switch(event.keyboard.mappedKey) {
								case fplKey_A:
								case fplKey_Left:
                                UpdateKeyboardButtonState(newKeyboardController->moveLeft, isDown);
                                break;
								case fplKey_D:
								case fplKey_Right:
                                UpdateKeyboardButtonState(newKeyboardController->moveRight, isDown);
                                break;
								case fplKey_W:
								case fplKey_Up:
                                UpdateKeyboardButtonState(newKeyboardController->moveUp, isDown);
                                break;
								case fplKey_S:
								case fplKey_Down:
                                UpdateKeyboardButtonState(newKeyboardController->moveDown, isDown);
                                break;
								case fplKey_Space:
                                UpdateKeyboardButtonState(newKeyboardController->actionDown, isDown);
                                break;
								case fplKey_F4:
                                UpdateKeyboardButtonState(newKeyboardController->debugToggle, isDown);
                                break;
								case fplKey_R:
                                UpdateKeyboardButtonState(newKeyboardController->debugReload, isDown);
                                break;
#if 0
								case fplKey_Return:
                                UpdateKeyboardButtonState(newKeyboardController->actionStart, isDown);
                                break;
#endif
								case fplKey_Escape:
                                UpdateKeyboardButtonState(newKeyboardController->actionBack, isDown);
                                break;
							}
						} else {
							fplDebugFormatOut("Frame %d, Key button press[%d] = %d\n", currentInput->frameIndex, event.keyboard.keyCode, event.keyboard.buttonState);
						}
						if(wasDown) {
							if(event.keyboard.mappedKey == fplKey_F) {
								bool wasFullscreen = fplIsWindowFullscreen();
								fplSetWindowFullscreen(!wasFullscreen, 0, 0, 0);
							}
						}
					} break;
				}
			} break;
		}
	}
}

#if 0
static uint32_t GameAudioPlayback(const fplAudioDeviceFormat *outFormat, const uint32_t frameCount, void *outputSamples, void *userData) {
	AudioSystem *audioSys = (AudioSystem *)userData;
	uint32_t result = AudioSystemWriteSamples(audioSys, outFormat, frameCount, (uint8_t *)outputSamples);
	return(result);
}
#endif

int main( int argc, char** argv ) {
    
	fplSettings settings = fplMakeDefaultSettings();
	settings.video.driver = fplVideoDriverType_OpenGL;
	settings.video.graphics.opengl.compabilityFlags = fplOpenGLCompabilityFlags_Legacy;
	settings.video.isVSync = true;
	fplCopyAnsiString( "test", settings.window.windowTitle, FPL_ARRAYCOUNT(settings.window.windowTitle));
    
	if(!fplPlatformInit(fplInitFlags_All, &settings)) {
		return -1;
	}
    
	bool wasError = false;
    
	if(!wasError) {
		const double TargetDeltaTime = 1.0 / 60.0;
        
		Input inputs[2] = {};
		Input *newInput = &inputs[0];
		Input *oldInput = &inputs[1];
		Vec2i lastMousePos = V2i(-1, -1);
        newInput->defaultControllerIndex = -1;
        
		uint32_t frameCount = 0;
		uint32_t updateCount = 0;
		double lastTime = fplGetTimeInSecondsHP();
		double fpsTimerInSecs = fplGetTimeInSecondsHP();
		double frameAccumulator = TargetDeltaTime;
		double lastFramesPerSecond = 0.0;
		double lastFrameTime = 0.0;
		int frameIndex = 0;
        
		while( fplWindowUpdate()) {
			// Window size
			fplWindowSize winArea;
			if(fplGetWindowArea(&winArea)) {
				newInput->windowSize.x = winArea.width;
				newInput->windowSize.y = winArea.height;
			}
            
			// Remember previous keyboard and mouse state
            Controller *oldKeyboardController = &oldInput->keyboard;
			Controller *newKeyboardController = &newInput->keyboard;
			*newKeyboardController = {};
			newKeyboardController->isConnected = oldKeyboardController->isConnected;
			newKeyboardController->actionDown.name = "KB-ActionDown";
            
			Mouse *newMouse = &newInput->mouse;
			Mouse *oldMouse = &oldInput->mouse;
			*newMouse = {};
			for(uint32_t buttonIndex = 0; buttonIndex < FPL_ARRAYCOUNT(newMouse->buttons); ++buttonIndex) {
				newMouse->buttons[buttonIndex] = oldMouse->buttons[buttonIndex];
				newMouse->buttons[buttonIndex].halfTransitionCount = 0;
			}
			newMouse->pos = lastMousePos;
            
			// Remember previous gamepad connected states
			for(uint32_t controllerIndex = 1; controllerIndex < FPL_ARRAYCOUNT(newInput->controllers); ++controllerIndex) {
				Controller *newGamepadController = &newInput->controllers[controllerIndex];
				Controller *oldGamepadController = &oldInput->controllers[controllerIndex];
				newGamepadController->isConnected = oldGamepadController->isConnected;
				newGamepadController->isAnalog = oldGamepadController->isAnalog;
			}
            
			for(uint32_t buttonIndex = 0; buttonIndex < FPL_ARRAYCOUNT(newKeyboardController->buttons); ++buttonIndex) {
				newKeyboardController->buttons[buttonIndex].endedDown = oldKeyboardController->buttons[buttonIndex].endedDown;
			}
            
			newInput->deltaTime = (float)TargetDeltaTime;
			newInput->framesPerSeconds = (float)lastFramesPerSecond;
			newInput->frameIndex = frameIndex++;
            
			// Events
            // newInput->frameIndex++;
            ProcessEvents(newInput, oldInput, lastMousePos);
			
            newInput->isActive = true;
            
            if ( WasPressed( newInput->keyboard.actionDown ) ) {
                
            }
            
			// Game Update
            
			// @TODO(final): Yield thread when we are running too fast
			double endWorkTime = fplGetTimeInSecondsHP();
			double workDuration = endWorkTime - lastTime;
            
			// Render
            fplVideoFlip();
			++frameCount;
            
			// Timing
			double endTime = fplGetTimeInSecondsHP();
			double frameDuration = endTime - lastTime;
			lastFrameTime = frameDuration;
			lastFramesPerSecond = 1.0f / frameDuration;
			
			lastTime = endTime;
			if(endTime >= (fpsTimerInSecs + 1.0)) {
				fpsTimerInSecs = endTime;
#if 0
				char charBuffer[256];
				fplFormatAnsiString(charBuffer, FPL_ARRAYCOUNT(charBuffer), "Fps: %d, Ups: %d\n", frameCount, updateCount);
				OutputDebugStringA(charBuffer);
#endif
				frameCount = 0;
				updateCount = 0;
			}
            
			// Swap input
			{
				Input *tmp = newInput;
				newInput = oldInput;
				oldInput = tmp;
			}
		}
    }
    
	fplStopAudio();
    
	fplPlatformRelease();
    
	int result = wasError ? -1 : 0;
	return (result);
}
152 posts / 1 project
I am finalspace and do programming since more than 25 years, started on C64 and got serious with borland delphi. Nowadays i use C/C++ only.
[Solved] Trouble with the handmade hero input system (HalfTransitionCount issue)
Edited by Finalspace on
@mrmixer:

Sure no problem, i updated the develop branch. Just check-it-out and build the demos/FPL_Crackout demo.
It should compile without any issues - hopefully, at least on MSVC and GCC.

Run it, and hit space bar until you are into the game. If everything works correct, two space presses are enough to get into the game.

I have this problem on every machine, WasPressed() never works reliably :(
If you introduce a delay (wait a few secs) before trying the next spacebar hit you will most likely get this bug. Uploaded a short video showing the problem: https://www.youtube.com/watch?v=L_q6UjCcMpA&feature=youtu.be
Simon Anciaux
1337 posts
[Solved] Trouble with the handmade hero input system (HalfTransitionCount issue)
It seems that the defaultControllerIndex of the two input struct is not synced.
In GameInput:
1
if(input.defaultControllerIndex != -1) {

is false half the time.
152 posts / 1 project
I am finalspace and do programming since more than 25 years, started on C64 and got serious with borland delphi. Nowadays i use C/C++ only.
[Solved] Trouble with the handmade hero input system (HalfTransitionCount issue)
Edited by Finalspace on
mrmixer
It seems that the defaultControllerIndex of the two input struct is not synced.
In GameInput:
1
if(input.defaultControllerIndex != -1) {

is false half the time.


Thanks, that was it! After remembering the defaultControllerIndex from the oldInput everything seems to work fine.

1
newInput->defaultControllerIndex = oldInput->defaultControllerIndex;


Also you have to init the oldInput->defaultControllerIndex to -1 as well, otherwise it will start out as 0.

Also i changed this:
1
bool result = ((state.halfTransitionCount > 1) || ((state.halfTransitionCount == 1) && (state.endedDown)));


To this - so it will only fire when the button is released after being pressed:
1
bool result = ((state.halfTransitionCount > 1) || ((state.halfTransitionCount == 1) && (!state.endedDown)));


Thanks a lot for catching that!