Tracking immediate property change with deffered events on?

I’m trying to lock an instance’s property to a certain value and caching any changes to it that are not made by my code so I can return it to the user set value. A problem I’m having with this is that the deffered signal behavior types cause my code to not work (I understand why).

I can’t just set the signal behavior to immediate because the code I’m writing is for a plugin. What I’m asking is if anyone knows a different / better way to do this that works with deffered events?

Here’s an example of my problem where I force the CanCollide property to alway be off.

--!strict

local function forceNoCollision(part: BasePart)
	local rawSet = false
	local function refreshCanCollide()
		rawSet = true
		part.CanCollide = false
		rawSet = false
	end
	
	local wasCanCollide = part.CanCollide
	local canCollideChanged = part:GetPropertyChangedSignal("CanCollide"):Connect(function()
		if not rawSet then
			wasCanCollide = part.CanCollide
			refreshCanCollide()
		end
	end)
	
	refreshCanCollide()
	
	return function()
		canCollideChanged:Disconnect()
		part.CanCollide = wasCanCollide
	end
end


local part = Instance.new("Part")
part.CanCollide = false

local unforce = forceNoCollision(part)

-- the user set this to true so when we unforce it will be true
part.CanCollide = true

unforce()

-- if signal behavior == immediate then true (correct!)
-- if signal behavior == deferred then false (wrong!)
print(part.CanCollide)
1 Like

Hi!

I was in a very similar situation when trying to modify TextBox.Text with each change the user typed. Deferred signals broke ‘rawSet’, so I started counting the changes where each even change would be made by users.

Code
local function forceNoCollision(part: BasePart)
	local wasCanCollide = part.CanCollide
	local n = 0
	
	part.CanCollide = false
	
	local canCollideChanged = part:GetPropertyChangedSignal("CanCollide"):Connect(function()
		n += 1
		if n % 2 == 0 then return end
		
		wasCanCollide = part.CanCollide
		part.CanCollide = false
	end)

	return function()
		canCollideChanged:Disconnect()
		part.CanCollide = wasCanCollide
	end
end

local part = Instance.new("Part")
part.CanCollide = false

local unforce = forceNoCollision(part)

part.CanCollide = true
task.defer(function()
	unforce()
	print(part.CanCollide)
end)

An alternative is to (re)connect :Once() upon modification.

Code
local function forceNoCollision(part: BasePart)
	local wasCanCollide = part.CanCollide
	local canCollideChanged
	
	local function OnCanCollideChanged()
		wasCanCollide = part.CanCollide
		part.CanCollide = false
		
		canCollideChanged = part:GetPropertyChangedSignal("CanCollide"):Once(OnCanCollideChanged)
	end
	
	OnCanCollideChanged()
	
	return function()
		canCollideChanged:Disconnect()
		part.CanCollide = wasCanCollide
	end
end

local part = Instance.new("Part")
part.CanCollide = false

local unforce = forceNoCollision(part)

part.CanCollide = true
task.defer(function()
	unforce()
	print(part.CanCollide)
end)

Some details

  • unforce() and print() are deferred because otherwise, when testing in the same script, the listener is disconnected before it gets the chance to catch the signal. A user who manages that should consider themselves extremely lucky.

  • Signals are optimised to not fire if a property is set to the same value, thus ‘false’ to ‘true’ works but ‘true’ to ‘false’ doesn’t, making counting and reconnecting no different than simply setting wasCanCollide to ‘true’ if the change occurs.
    → I’d consider adding some sort of a separate gui switch or shortcut for modifying the state, at least while it’s locked.

I hope this helps!

1 Like

This is exactly what I was looking for! Thank you for the detailed response I can def work with this!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.