GDScript Oddities with MultiTapHandler Class

Godot Version

v4.7.stable.official [5b4e0cb0f]

Question

I’m trying to make a MultiTapHandler class so that I can handle multi-taps (my eventual goal is to have it so if you tap shift twice then you sprint instead of just running). However, some bad things are misbehaving and I’m unsure how to solve the issue.

Here’s my code for MultiTapHandler.gd:

class_name MultiTapHandler extends Node;

"""
This is a class I made up because I
hate coding

Basically, this is just a handler for
multi-taps. So like if you double-tap
a key and there's a handler active
for it, it'll do that and you can
detect that with the `has_tapped()`
function. Anyway yeah yap sesh over
"""

var action_name:String = "";
var action_window:float = 1.0;

var taps:Array[float] = [];
var disabled:bool = false;

func _init(action:String, window:float) -> void:
	action_name = action;
	action_window = window;
	
func _process(_delta: float) -> void:
	if (action_name == "" || !InputMap.has_action(action_name)):
		print("You need a valid action, dumb-dumb");
		return;
		
	if (Input.is_action_just_pressed(action_name)):
		taps.append(float(Time.get_ticks_msec()) / 1000);
		
	var curTime:float = float(Time.get_ticks_msec()) / 1000;
	taps = taps.filter(func(tapTime):
		return (curTime - tapTime) <= action_window;
	);
	
func tapped_within(times:int, clear:bool) -> bool:
	var success:bool = taps.size() >= times;
	if (success && clear):
		clear_taps();

	return success;

func tapped_exact(times:int, clear:bool) -> bool:
	return (tapped_within(times, clear) && taps.size() == times);

# hjehehehehe somebody is gonna see this and kill me
func clear_taps() -> void: return taps.clear();
func toggle() -> bool: disabled = !disabled; return disabled;

func debugPrint() -> String:
	return "\n".join([
		'Action Name: %s' % action_name,
		'Action Window: %s' % action_window,
		'Taps: [%s]' % ", ".join(
			taps.map(func(tapTime): return str(tapTime))
		),
	]);

This is my implementation of the handler for Player.gd. Not all code in Player.gd is shown here, at least the stuff that shouldn’t be required for the handler to work:

extends CharacterBody3D

var shiftTapHandler:MultiTapHandler;

func _ready() -> void:
	shiftTapHandler = MultiTapHandler.new("movement_quick", 0.3); # `movement_quick` is set to Shift (Physical) by default
	add_child(shiftTapHandler);
	
func _physics_process(_delta: float) -> void:		
	print(shiftTapHandler.debugPrint()) #you'll never guess

At the moment, pressing shift does nothing and taps:Array[float] isn’t populating with tap times. I’m kind of at a loss for what to do here; in my experience with this project, Input things in general have been kinda iffy for me. Please help if possible!

Don’t override _init it almost always breaks things, especially Node classes. Make the new node, then change it’s properties before adding, any initialization you need can be done on _ready which is called by add_child

I wound up not extending Node and just iterating through every MultiTapHandler in a global script to call update(). Thank you for the help though!