Solved [1.16.x] Asynchronous raytracing problems

Discussion in 'Spigot Plugin Development' started by Cerus, Dec 29, 2020.

  1. Hello everyone,

    I'm currently working on a plugin that heavily utilizes raytracing. Because this can cause lag I'd like to move the raytracing out of the server thread. I'm running into strange performance issues when calling the rayTraceBlocks method from another thread though.
    I know that the Minecraft server is not great at multithreading so I suspect that that's the reason why the performance drops this drastically.

    I've recorded a little comparison so you can see the problem:

    As you can see in the video the map changes almost instantly when doing the raytracing on the server thread. When the raytracing is done on another thread though the map takes around 6 seconds change.

    I'd like to move the raytracing out of the server thread and still get nearly instant results. Is there any way I can use Bukkits raytracing method for this? I don't really want to write my own raytracing method but it seems like I might have to do exactly that.

    I'm running on 1.16.3 by the way. Thanks!

    Edit: Code:
    Raytracing code - https://gist.github.com/cerus/ab4b4b687aca44ff3c376ce24e2bd2fa
    Calling the raytracing code - https://gist.github.com/cerus/2e54686297980a2d7fda4f29bc29c843

    Edit 2:
    Click here to jump to the solution
     
    #1 Cerus, Dec 29, 2020
    Last edited: Dec 31, 2020
    • Optimistic Optimistic x 1
  2. You may be spawning too many threads which can eat too many resources or cause overhead issues. I would suggest you use a thread pool to handle the the image processing.

    What is your current approach to making it multi-threaded?
     
  3. I've tried Bukkits scheduler and I've tried a single threaded executor service - both had the same result as the one in the video.
     
  4. What thread pool size are you using?

    Could you post any code? There isn't much to suggest without seeing your implementation.
     
  5. Well, I'd basically like to know if there's any way that I can use Bukkits raytracing method async or if I have to write my own method but here's the snippet: https://gist.github.com/cerus/2e54686297980a2d7fda4f29bc29c843
    And here's the raytracing code: https://gist.github.com/cerus/ab4b4b687aca44ff3c376ce24e2bd2fa

    I'm getting my executor service from Executors#newSingleThreadExecutor() so it's bascially a thread pool with only one thread.
     
    #5 Cerus, Dec 29, 2020
    Last edited: Dec 29, 2020
  6. Does that code actually work? I would think that it would error out for trying to run Bukkit API methods on a separate thread. Or does it work but it is just slow?
     
  7. It does work but it is much slower when run on another thread. You can watch the video that I've linked in my first post to see the difference.
     
  8. Have you done any investigating into what is causing the slow down? Maybe try looking into the timings of each part of the for loop and see if one if much slower than the other?
     
  9. It definitely seems like the raytracing call is taking the most time. I did look into the timings but I can't find anything unusual. Here are the timings if you want to take a look yourself: https://timings.aikar.co/?id=df8fe2f02e984c759b07878ab907bec4#timings
     
  10. I have already tried that and it doesn't make a difference unfortunately.
     
  11. I had this exact same problem when trying to do the exact same thing, what a coincidence!

    That happens because of a check the server performs before accessing chunk data. If that happens from outside the main thread, the server will delegate the access to the main thread and wait for a response.
    [​IMG]
    So, if you do that from another thread, you're essentially limiting it to one ray cast per tick, which is why it takes so much more time.

    In my case, I resorted to ChunkSnapshots, which are the way to go if you're accessing world asynchronously. However, you will need to adapt your code accordingly.
    I haven't given it an in-depth look, but you may have to write your own raytracing code in the worst case scenario (as the default one seems to be heavily coupled to NMS code).
     
    • Agree Agree x 1
    • Winner Winner x 1
    • Informative Informative x 1
  12. I'll try to extract the raytracing code and modify it to use chunk snapshots. Thank you for pointing me into this direction! I'll update this thread when I'm done.
     
  13. After digging around the Craftbukkit source for a few hours I finally managed to create a working async solution. (NMS is required unfortunately)
    To avoid writing my own raytracing method I decided to cache NMS chunks instead of chunk snapshots. The caching needs to be done synchronously.

    1. Extend the IBlockAccess interface and override the getType, getFluid and getTileEntity methods to use your cached chunks (Example)
    2. Use your IBlockAccess implementation for ray tracing (see IBlockAccess#rayTrace()) (Example)
    3. Now you're able to asynchronously ray trace using your implementation!

    Important: Calls like Block#getType(), Block#getState() and Block#getBlockData() all make calls to their chunk. If these calls are done async you will run into the limitation of one chunk call per tick (see @WinX64's answer). Make sure to use your chunk cache for async type, state or data retrieval. (Example)
     
    • Winner Winner x 1
    • Informative Informative x 1