Question about handling multiple events/firing an event under certain circumstances

Hello!

I created a basic module that listens to multiple events but proceeds when only one fires. It works decently but the problem is that because of how .MouseButton1Up works, I had to resort to using .InputEnded but the problem is that it would work with any input, not just the mouse button. Any help?

Multi-event handler:

local module = {
	['new'] = function(...)
		local rbxScriptSignals = {...}
		local connections = {}
		local signal = Instance.new('BindableEvent')
		for i,v in pairs(rbxScriptSignals) do
			if typeof(v) == 'RBXScriptSignal' then
				connections[#connections + 1] = v:Connect(function()
					for i,v in pairs(connections) do
						v:Disconnect()
					end
					signal:Fire()
				end)
			end
		end
		return signal.Event
	end;
}

return module

Second script, a script that controls slider UI element objects.

local players = game:GetService('Players')
local userInputService = game:GetService('UserInputService')
local localPlayer = players.LocalPlayer

local multiEventHandler = require(script.Parent:WaitForChild('MultiEventHandler'))
local module = {
	['CreateSlider'] = function(self, object)
		local bounds = object:GetAttribute('Bounds')
		local sliderBounds = object:FindFirstChild('SliderBounds')
		local sliderConnections = {}
		local mouse = localPlayer:GetMouse()
		if sliderBounds then
			local notch = sliderBounds:FindFirstChild('Notch')
			if notch then
				sliderBounds.MouseButton1Down:Connect(function()
					local currentConnections = {}
					currentConnections[#currentConnections + 1] = mouse.Move:Connect(function()
						local y = mouse.Y
						local topBoundary = 0
						local bottomBoundary = sliderBounds.AbsoluteSize.Y
						local mousePos = y - sliderBounds.AbsolutePosition.Y
						notch.Position = UDim2.new(
							notch.Position.X.Scale,
							notch.Position.Y.Scale,
							0, 
							math.clamp(mousePos, topBoundary, bottomBoundary)
						)
					end)

-- IMPORTANT PART STARTS HERE

					multiEventHandler.new( -- here, it creates the multi-instance signal
						userInputService.TouchEnded,
						sliderBounds.MouseButton1Up
					):Wait()
					for i,v in pairs(currentConnections) do
						v:Disconnect()
					end
				end)
			else
				return warn('No Notch for UI element',object)
			end
		else
			return warn('No SliderBounds for UI element',object)
		end
	end;
}

return module

The thing is, I could go about creating a sort of “flag” system but I just wanted to know if anyone had any ideas/an easier way to go about doing it.

1 Like

One way to achieve this is by writing a function that filters events by a predicate:

function filter_event(predicate, event)
	local filtered_event = Instance.new("BindableEvent")
	spawn(function()
		while true do
			local result = table.pack(event:Wait())
			if predicate(table.unpack(result)) then
				filtered_event:Fire(result)
			end
		end
	end)
	return filtered_event.Event
end

Then you can create a new event that only fires when the left mouse button is released as such:

local UserInputService = game:GetService("UserInputService")

local mouse_up = filter_event(function(input)
	return input.UserInputType == Enum.UserInputType.MouseButton1
end, UserInputService.InputEnded)

Oh yeah, I was thinking of doing something like that and came up with this:

local module = {
	['new'] = function(instance, eventName, func)
		local signal = Instance.new('BindableEvent')
		local connection 
		connection = instance[eventName]:Connect(function(...)
			if func(...) then
				connection:Disconnect()
				signal:Fire()
			end
		end)
		return signal.Event
	end;
}

return module

Then from the other script,

                        eventHandler.new(
							userInputService,
							'InputEnded',
							function(key, isSystemReserved)
								if key.UserInputType == Enum.UserInputType.MouseButton1 then
									return true
								end
							end
						)

I’m a little confused though, why would I have to create an alternate thread and keep setting the result to the event’s returned arguments?

Your solution works too!

You totally don’t have to. Both of our solutions do the same thing except each one is the inside-out version of the other with respect to control flow. Personally I avoid the inverted control structure that comes with using :Connect() when writing concurrent code like this and instead use explicit threads. Different strokes.

Tangent: maybe you already know this but your “multi-event handler” implementation is basically 90% of the way to the select statement from Go.

Along the same lines, Concurrent ML is an older and less popular but arguably more powerful language (technically library) than Go for writing concurrent programs, and it also has a non-deterministic event choice operator called select that has essentially the same implementation as your multi-event handler. There’s even an implementation of Concurrent ML in Lua.

My style of concurrent programming is heavily influenced by the book Concurrent Programming in ML (ISBN-10: 0521714729), which is 100% percent worth reading if you ever want to dig deep into concurrent programming.

1 Like