Jump to content

Scanning world - infinite running script


Dzsozi (h03)

Recommended Posts

Hello!

I would like to get the positions of all the lampposts. I was trying to use processLineOfSight to achieve this, since getElementsByType("object") doesn't return default world objects.

I tried using a loop from 0 to 3000 on the x and y axis, but I get an infinite running script error. Why do I get this error and how could I achieve a scan on the world? Here's my current code:

(I commented out most parts while trying to find the cause of the problem, so I would only see the positions of all the lamps if the scan was successful)

local STREET_LIGHTS = {

	VALID_MODELS = {
		[1294] = {name = "mlamppost", offsets = {Vector3(0,0.3,1)}, radius = {start = 0.075, stop = 50}},
		[1297] = {name = "lamppost1", offsets = {Vector3(0,0.3,1)}, radius = {start = 0.075, stop = 50}},
		[1290] = {name = "lamppost2", offsets = {Vector3(3,0.03,5.4), Vector3(-3,0.03,5.4)}, radius = {start = 0.075, stop = 75}},
		[1226] = {name = "lamppost3", offsets = {Vector3(-1.3,0.165,3.675)}, radius = {start = 0.075, stop = 50}},
	},
	
	OBJECT_LIGHTS = {},
}

function processStreetLights()
	for x = 0, 3000, 5 do
		for y = 0, 3000, 5 do
			local hit, _,_,_,_,_,_,_,_,_,_,worldModelID,worldX,worldY,worldZ,worldRotX,worldRotY,worldRotZ,_ = processLineOfSight(
				x, y, 0,
				x + 5, y + 5, 500,
				true, false, false, true, false,
				false, false, false, nil, true, false
			)
			
			if hit then
				--[[if STREET_LIGHTS.VALID_MODELS[worldModelID] then
					local data = STREET_LIGHTS.VALID_MODELS[worldModelID]
					local searchlights = {}
					
					for i, offset in pairs(data.offsets) do
						local startPos = Vector3(worldX, worldY, worldZ + offset.z) --Vector3(exports["sa_utility"]:getPositionFromElementOffset(obj, offset.x, offset.y, offset.z))
						
						startPos.x = worldX + math.sin( math.rad( worldRotZ ) ) * offset.x
						startPos.y = worldY + math.cos( math.rad( worldRotZ ) ) * offset.y
						
						local endPos = startPos --Vector3(exports["sa_utility"]:getPositionFromElementOffset(obj, offset.x, offset.y, 0.25))
						endPos.z = worldZ + 0.25
						
						searchlights[i] = SearchLight(startPos, endPos, data.radius.start, data.radius.stop, true)
					end
					
					--STREET_LIGHTS.OBJECT_LIGHTS[obj] = searchlights
					table.insert(STREET_LIGHTS.OBJECT_LIGHTS, searchlights)
				end]]
				print(worldX, worldY, worldZ)
			end
		end
	end
	

	--[[for k, obj in pairs(getElementsByType("object")) do
		if STREET_LIGHTS.VALID_MODELS[obj.model] then
			local data = STREET_LIGHTS.VALID_MODELS[obj.model]
			local searchlights = {}
			
			for i, offset in pairs(data.offsets) do
				local startPos = Vector3(exports["sa_utility"]:getPositionFromElementOffset(obj, offset.x, offset.y, offset.z))
				local endPos = Vector3(exports["sa_utility"]:getPositionFromElementOffset(obj, offset.x, offset.y, 0.25))
				
				searchlights[i] = SearchLight(startPos, endPos, data.radius.start, data.radius.stop, true)
			end
			
			STREET_LIGHTS.OBJECT_LIGHTS[obj] = searchlights
		end
	end]]
	
	return true
end

addEventHandler("onClientResourceStart", resourceRoot, function()
	processStreetLights()
	return true
end)

Thank you for help in advance!

Link to comment
  • Moderators
6 hours ago, Dzsozi (h03) said:

Why do I get this error and how could I achieve a scan on the world?

There is a limit code can run for X amount of time each frame. If exceed this amount, the code will abort and you will receive this error.

You can use a coroutine in order to temporary stop your code and resume in the next frame.

local co
co = coroutine.create(function ()
    -- your code START
    
	----------------------
	-- inner loop START --
    
		-- if getTickCount > ... then

			callNextFrame(function () 
				coroutine.resume(co)
			end)
			coroutine.yield()
		--
    
	-- inner loop END --
	----------------------
    
	
	
    -- code END
end)

-- start
coroutine.resume(co)

 

Calling next frame:

https://gitlab.com/IIYAMA12/mta-communication-enchantment/-/blob/master/sync/sync_shared.lua#L421

(note there are some variables that are defined above the function)

 

Link to comment
On 14/11/2022 at 18:19, IIYAMA said:

Is everything clear or do you have any questions? @Dzsozi (h03)

Sorry I didn’t make time to reply, my fault. Actually it is not so clear, I don’t really know how to use coroutine functions, I never did before. Not so sure how to implement it for the purpose I need.

On 15/11/2022 at 12:11, thisdp said:

by the way, you need to move your camera at the same coordinate of processLineOfSight, because of the streaming system.

Thank you for the advice I didn’t even think about that and actually makes sense to do it.

Link to comment
  • Moderators
1 hour ago, Dzsozi (h03) said:

. Actually it is not so clear, I don’t really know how to use coroutine functions

 

A coroutine is a kind of thread type, which you can use to put a function to a temporary stop.

The coroutine.create method requires a function and returns the coroutine.

local co
co = coroutine.create(function ()
end)

 

The coroutine.yield method stops the thread, when it is called inside of the function.

local co
co = coroutine.create(function ()
    coroutine.yield()
end)

 

To start a coroutine you can must call the method coroutine.resume, the first argument has to be the coroutine.

local co
co = coroutine.create(function ()
    print("A")
    coroutine.yield()
    print("B")
end)

coroutine.resume(co) -- print A
-- but does NOT print B

 

To resume the coroutine after yield, just call coroutine.resume again.

local co
co = coroutine.create(function ()
    coroutine.yield()
    print("B")
end)

coroutine.resume(co) -- start
-- coroutine.yield()
coroutine.resume(co) -- print B

 

Add an async function of any kind (timer) and you are ready to go.

local co
co = coroutine.create(function ()
    print("A")
    coroutine.yield()
    print("B")
end)

coroutine.resume(co) -- print A

		
setTimer(function () 
    coroutine.resume(co) -- print B
end, 5000, 1)	

 

 

 

  • Like 1
Link to comment
On 16/11/2022 at 19:36, IIYAMA said:

 

A coroutine is a kind of thread type, which you can use to put a function to a temporary stop.

 

Thank you for teaching me, really, I tried to make the scanning with these functions, I get an error spam "cannot resume dead coroutine" because of the timer (this line)

STREET_LIGHTS.SCAN_TIMER = setTimer(resumeScan, 1000, 0)

Here's my current code, could you help me with what am I doing wrong?

local STREET_LIGHTS = {
	VALID_MODELS = {
		[1294] = {name = "mlamppost", offsets = {Vector3(0,0.3,1)}, radius = {start = 0.075, stop = 50}},
		[1297] = {name = "lamppost1", offsets = {Vector3(0,0.3,1)}, radius = {start = 0.075, stop = 50}},
		[1290] = {name = "lamppost2", offsets = {Vector3(3,0.03,5.4), Vector3(-3,0.03,5.4)}, radius = {start = 0.075, stop = 75}},
		[1226] = {name = "lamppost3", offsets = {Vector3(-1.3,0.165,3.675)}, radius = {start = 0.075, stop = 50}},
	},
	
	OBJECT_LIGHTS = {},
}

local SCAN = {
	current = Vector2(-3000, -3000),
}

local SCAN_WORLD = coroutine.create(function()
	for x = SCAN.current.x, 3000, 10 do
		if math.abs(x) % 100 == 100 then SCAN.current.x = x print"yield" coroutine.yield() end
		
		for y = SCAN.current.y, 3000, 10 do
			if SCAN.current.x  >= 3000 and SCAN.current.y >= 3000 then
				if isTimer(STREET_LIGHTS.SCAN_TIMER) then killTimer(STREET_LIGHTS.SCAN_TIMER) end
				STREET_LIGHTS.SCAN_TIMER = nil
				setCameraTarget(localPlayer)
				coroutine.yield()
				break
			end
			
			if math.abs(y) % 100 == 100 then SCAN.current.y = y coroutine.yield() end
			setCameraMatrix(x, y, 500, x, y, 0)
			
			local hit, _,_,_,_,_,_,_,_,_,_,worldModelID,worldX,worldY,worldZ,worldRotX,worldRotY,worldRotZ,_ = processLineOfSight(
				x, y, 500,
				x + 5, y + 5, 0,
				true, false, false, true, false,
				false, false, false, nil, true, false
			)
			
			if hit then
				--if STREET_LIGHTS.VALID_MODELS[worldModelID] then
					print(worldModelID) -- do i actually get the models on screen? i get some numbers for sure but camera is stuck and position is not updating
				--end
			end
		end
	end
	return true
end)

local function resumeScan()
	return coroutine.resume(SCAN_WORLD)
end

addEventHandler("onClientResourceStart", resourceRoot, function()
	if not isTimer(STREET_LIGHTS.SCAN_TIMER) then
		STREET_LIGHTS.SCAN_TIMER = setTimer(resumeScan, 1000, 0)
	end
	
	return true
end)

And actually I don't even get the "yield" output as expected from this line:

if math.abs(x) % 100 == 100 then SCAN.current.x = x print"yield" coroutine.yield() end

Camera is stuck at one place on the top right corner of the map and first I get a bunch of spam of 3 or 4 numbers, then the error about cannot resume dead coroutine.

Edited by Dzsozi (h03)
Link to comment
  • Moderators
54 minutes ago, Dzsozi (h03) said:

Camera is stuck at one place on the top right corner of the map and first I get a bunch of spam of 3 or 4 numbers, then the error about cannot resume dead coroutine.

A dead coroutine means that this:

local SCAN_WORLD = coroutine.create(function()

end)

function has ended it's execution in some way.
Either by

  • an error
  • or just running until the very end.

I recommend to start the next async call just before the yield. This will eliminate the problems of resuming dead coroutines.

So:

STREET_LIGHTS.SCAN_TIMER = setTimer(resumeScan, 1000, 0)

addEventHandler("onClientResourceStart", resourceRoot, function()
	if not isTimer(STREET_LIGHTS.SCAN_TIMER) then
		STREET_LIGHTS.SCAN_TIMER = setTimer(resumeScan, 1000, 1)
	end
	
	return true
end)

 

Run the code for X amount of time and start the resume timer there:
(see a version of your current method below)

local MAX_EXECUTION_INTERVAL = 3 -- ms
local maxExecutionTime = 0
-- inside of the loop
if getTickCount() > maxExecutionTime then
	STREET_LIGHTS.SCAN_TIMER = setTimer(resumeScan, 1000, 1)
	coroutine.yield() 
end

You might consider balance the STREET_LIGHTS.SCAN_TIMER timer out with the max execution time.

function resumeScan()
	maxExecutionTime = getTickCount() + MAX_EXECUTION_INTERVAL
	return coroutine.resume(SCAN_WORLD)
end

 

--->

 

54 minutes ago, Dzsozi (h03) said:
if math.abs(x) % 100 == 100 then SCAN.current.x = x print"yield" coroutine.yield() end
math.abs(x) % 100 == 100

This will never be true :)

100 % 100 = 0
99 % 100 = 99
101 % 100 = 1
500 % 100 = 0

You might consider using 0

math.abs(x) % 100 == 0

 

if math.abs(x) % 100 == 0 then 
  SCAN.current.x = x 
  print"yield" 
  STREET_LIGHTS.SCAN_TIMER = setTimer(resumeScan, 1000, 1)
  coroutine.yield() 
end

 

  • Like 1
Link to comment

Sorry for bumping this topic that late, I got some free time now,  I still wasn’t able to finish this project based on the examples, also I just realised that the scan will be going diagonally if I add to x and y at the same time, therefore the scan wouldn’t apply to the whole world.

I would like to achieve an effect of scanning such as from the top left corner of the map to the bottom right, going sideways, and when the camera reaches the right side, it snaps back to the left and gets offset on the y axis.

I am not sure about how to achieve this between the range of -3000 to 3000 on the x and y axis inside a coroutine function with loops, am I even doing it the right way by using for loop? I would have to prevent the loop from happening on the y axis before the scanX reaches the right side.

Right now I can’t send my current code and I can’t remember what errors I got, I’m at work but I wanted to reply and let you know that I’m still interested and trying to fix & get it done.

I will post my code asap, could you please maybe help me finishing and correcting it?

Link to comment
  • Discord Moderators

I read your original post and you wanted position of lamp posts.

It would be a lot simpler to get this data from the .ipl files which contain placement of world models.

These files are in the data folder of gta sa.

  • Thanks 1
Link to comment

This is a great idea and would be much simpler and easier indeed, now that you said I realised it. But I think the reason I wanted to do a scanning type solution is because of the custom placed objects (i know I can check those and I already did in the original script), but also, more likely because of the removed world models. What would you suggest to check that, if I were doing the solution you mentioned? I have some custom maps which for sure remove some lamps, I made some that removes even a whole street.

Link to comment
  • Discord Moderators

3 solutions

If you are removing the models from a script

1. Have the scripts call a central resource to remove the models. If they are removing the lamp post ID, store the position and radius in a table.
2. Manually store the position and radius in a table.

If you are removing the models with map editor (.map files)

3. getElementsByType("removeWorldObject") will return custom elements that represent each removal. Use getElementData on each element and get posX, posY, posZ and radius data fields, on the ones with the right model.

Once you have the x, y, z and radius of the world removals, you can skip IPL entries of your lamp post that contain a position within this sphere (defined by x, y,z  and radius).
To determine if a point is within a sphere, it's a simple distance check

x1, y1, z1
x2, y2, z2
radius

distance = math.sqrt((x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2)
if distance < radius then
	-- Point is within sphere
else
	-- Point is outside
end

 

  • Like 1
Link to comment
On 28/11/2022 at 07:43, Zango said:

3. getElementsByType("removeWorldObject") will return custom elements that represent each removal. Use getElementData on each element and get posX, posY, posZ and radius data fields, on the ones with the right model.

Thank you, I didn't know I could do this with getElementsByType ?

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...