+1 vote

Hello!

I have been following some tutorials on YouTube to generate landscape from height maps. As my code does not seem very performant - generating anything over like a 300x300 block crashes the algorithm, I wanted to implement threading to generate the landscape in the background.

I am not familiar with threading at all really, and I have created a rudimentary solution that seems to work okay, my only issue is that it seems to randomly crash with unhelpful error messages, or at least, error messages that I don't know how to solve.

This is the code for my Terrain scene:

extends Spatial
export(OpenSimplexNoise) var noise
export(float) var noiseScale
export(bool) var regenerate setget regenSet
export(int) var chunkSize
export(int) var mapWidth
export(int) var mapHeight


var mapLoading = true
var mutex
var threads = []
var lazyThreads = []
var workList = []

var numberThreads = 8






func generateMap(cs, mw, mh):
    mutex = Mutex.new()
    # Make threads for map generator
    for i in numberThreads:
        var tempThread = Thread.new()
        print("Made thread: ", tempThread.is_active())
        lazyThreads.push_back(tempThread)



    # Make data for map generator
    for i in mh:
        for j in mw:
            var xoffset = i * cs
            var yoffset = j * cs
            var threadData = [cs, xoffset, yoffset, noise, noiseScale, mutex]
            workList.push_front(threadData)





func assignLazyThreads():

    var lazy = lazyThreads.pop_back()
    if lazy != null:
        # We have a thread
        mutex.lock()
        var thrdData = workList.pop_back()
        mutex.unlock()
        if thrdData != null:
            print("Thread: ", lazy)
            thrdData.push_back(lazy)
            if lazy.is_active():
                lazy.wait_to_finish()

            lazy.start(self, "getChunk", thrdData)



func _process(_delta):
    # 2 thread lists, lazy and active
    assignLazyThreads()








func getChunk(data):
    print("Getting chunk for data: ", data)
    mutex.lock()
    var MapGen = load("res://MapGenerator.gd").new()
    mutex.unlock()
    MapGen.setChunk(data[0])
    var chunkData = MapGen.generateLandscape(data)  

    var chunkMesh = MeshInstance.new()
    chunkMesh.mesh = chunkData[0]
    call_deferred("add_child", chunkMesh)
    print("Thread has finished")
    mutex.lock()
    lazyThreads.push_back(data[6])
    mutex.unlock()




func clearMap():
    for i in range(0, get_child_count()):
        get_child(i).queue_free()   



func _ready():
    clearMap()
    generateMap(chunkSize, mapWidth, mapHeight) 


func regenSet(_regen):
    regenerate = true
    print("Regenerating map")
    clearMap()
    generateMap(chunkSize, mapWidth, mapHeight)

And the code for my MapGenerator:

extends Spatial

var chunkSize
var width
var height
var noise = null
var noiseScale
var tempMesh
var vertices
var UVs
var normals
var mutex

func createQuad(x, y):
    var vert1 # vertex positions (Vector2)
    var vert2
    var vert3

    var side1 # sides of each triangle (Vector3)
    var side2

    var normal # normal for each triangle (Vector3)

    # triangle 1
    vert1 = Vector3(x, getNoise(x, -y), -y)
    vert2 = Vector3(x,   getNoise(x, -y-1), -y-1)
    vert3 = Vector3(x+1, getNoise(x+1, -y-1), -y-1)
    vertices.push_back(vert1)
    vertices.push_back(vert2)
    vertices.push_back(vert3)

    UVs.push_back(Vector2(vert1.x/10, -vert1.z/10))
    UVs.push_back(Vector2(vert2.x/10, -vert2.z/10))
    UVs.push_back(Vector2(vert3.x/10, -vert3.z/10))

    side1 = vert2-vert1
    side2 = vert2-vert3
    normal = side1.cross(side2)

    for _i in range(0,3):
        normals.push_back(normal)

    # triangle 2
    vert1 = Vector3(x, getNoise(x, -y), -y)
    vert2 = Vector3(x+1, getNoise(x+1, -y-1), -y-1)
    vert3 = Vector3(x+1, getNoise(x+1, -y), -y)
    vertices.push_back(vert1)
    vertices.push_back(vert2)
    vertices.push_back(vert3)

    UVs.push_back(Vector2(vert1.x/10, -vert1.z/10))
    UVs.push_back(Vector2(vert2.x/10, -vert2.z/10))
    UVs.push_back(Vector2(vert3.x/10, -vert3.z/10))

    side1 = vert2-vert1
    side2 = vert2-vert3
    normal = side1.cross(side2)

    for _i in range(0,3):
        normals.push_back(normal)


func getNoise(x, y):
    if noise != null:
        return noise.get_noise_2d(x, y) * noiseScale
    else:
        return 0

func setChunk(size):
    chunkSize = size

func generateLandscape(chunkData):
    var xoffset = chunkData[1]
    var yoffset = chunkData[2]
    noise       = chunkData[3]
    noiseScale  = chunkData[4]
    mutex = chunkData[5]

    tempMesh = Mesh.new()
    vertices = PoolVector3Array()
    UVs      = PoolVector2Array()
    normals  = PoolVector3Array()

    for x in range(0, chunkSize):
        for y in range(0, chunkSize):
            createQuad(x + xoffset, y + yoffset)

    var st = SurfaceTool.new()
    st.begin(Mesh.PRIMITIVE_TRIANGLES)
    mutex.lock()
    st.set_material(load("res://mat.tres"))
    mutex.unlock()

    for v in vertices.size():
        st.add_uv(UVs[v])
        st.add_normal(normals[v])
        st.add_vertex(vertices[v])

    st.commit(tempMesh)
    var shape = ConcavePolygonShape.new()
    shape.set_faces(tempMesh.get_faces())
    return [tempMesh, shape]

My code is also hosted here: https://github.com/thummper/Massive if you would like to download and run it - generally I get errors after changing the size of the chunks, of the size of the map and rerunning the code.

My errors look like this https://i.imgur.com/qczqVP9.png:
Errors

Any help in solving these errors would be appreciated, also any help with the structure of my code would be helpful as I have never done any multithreaded stuff before - initially I was just making a new thread each time I needed a new chunk, but that led to 100% CPU usage, so I thought limiting the threads would be a better solution.

Thanks.

in Engine by (50 points)

Please log in or register to answer this question.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.