Jump to content

Particle memory management


Ilandria

Recommended Posts

Hey,

 

I'm working on adding custom particles to some of my blocks, and I've gotten it working using a few different methods. However, all of them run into issues related to garbage collection/memory inefficiencies.

 

Currently what I'm doing is the following:

I create an object pool of particles on load (with the intent of not spamming GC memory) and trigger the effects from that pool. If it runs out of available particles it just loops and overwrites the first one:

In ClientProxy.java:
    @SubscribeEvent
	public static void onPostTextureStitch(TextureStitchEvent.Post a_event)
	{
		GlitterParticle[] particles = new GlitterParticle[500];

		for (int i = 0; i < particles.length; i++)
		{
			particles[i] = new GlitterParticle();
		}

		s_glitterEmitter = new Emitter(particles);
	}


In Emitter.java:
	@SideOnly(Side.CLIENT)
	public class Emitter<T extends SimpleParticle>
	{
		private SimpleParticle[] m_particles = null;
		private int m_nextIndex = 0;

		public Emitter(SimpleParticle[] a_particles)
		{
			m_particles = a_particles;
		}

		public void emit(World a_world, double a_x, double a_y, double a_z, Color a_colour)
		{
			SimpleParticle particle = m_particles[m_nextIndex++ % m_particles.length];
			particle.emit(a_world, a_x, a_y, a_z, a_colour);
			Minecraft.getMinecraft().effectRenderer.addEffect(particle);
		}
	}
    
emit() in SimpleParticle:
	public void emit(World a_world, double a_x, double a_y, double a_z, Color a_colour)
	{
		world = a_world;
		particleAge = 0;
		isExpired = false;
		posX = a_x;
		posY = a_y;
		posZ = a_z;
		prevPosX = posX;
		prevPosY = posY;
		prevPosZ = posZ;
		particleRed = a_colour.getRed() / 255.0f;
		particleGreen = a_colour.getGreen() / 255.0f;
		particleBlue = a_colour.getBlue() / 255.0f;
	}
    
Aaaand... How it's being used in randomDisplayTick on my blocks:
    if(a_world.isAirBlock(new BlockPos(x, y, z)))
	{
		ClientProxy.s_glitterEmitter.emit(a_world, x, y, z, colour);
	}

 

I figured if I made my own pool of particles and added them to the effect renderer on emission, it wouldn't make copies of particles and thus get around spamming tons of GC memory, and if I tried to add a duplicate particle it simply wouldn't add it to the renderer until the previous reference to it was "finished" and removed. I was sad when I looked into ParticleManager and found that no such precautions are built-in. My knowledge on Java's GC and value vs reference types definitely could be stronger, I'm mostly drawing from my experience with C# for this.

 

This is a concern for me since lots of the blocks and items in my mod emit particles and I'd like to make sure I'm doing things efficiently without allocating literal hundreds of megs of GC memory every few seconds just for particles that could easily be pre-allocated and pooled during load. If there's no built-in MC/Forge way of doing this, would anyone be able to suggest how I can do my own fully-custom rendering within the MC/Forge environment so I can be memory-efficient?

 

Thank you! :D

Edited by Ilandria
Link to comment
Share on other sites

Don't your particles "die" after a couple seconds? I think the vanilla system just relies on the fact that a small number of short-lived particles are in the world at any time. If you're producing particles faster than they die then that would be a problem no matter what. And the steady-state memory usage depends heavily on the ratio of rate of particles emitting versus particles dying.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Link to comment
Share on other sites

1 hour ago, jabelar said:

Don't your particles "die" after a couple seconds? I think the vanilla system just relies on the fact that a small number of short-lived particles are in the world at any time. If you're producing particles faster than they die then that would be a problem no matter what. And the steady-state memory usage depends heavily on the ratio of rate of particles emitting versus particles dying.

There’s a proper limit of 16,384 (from memory) particles allowed at any one time. If another particle is spawned the oldest existing particle is killed

About Me

Spoiler

My Discord - Cadiboo#8887

My WebsiteCadiboo.github.io

My ModsCadiboo.github.io/projects

My TutorialsCadiboo.github.io/tutorials

Versions below 1.14.4 are no longer supported on this forum. Use the latest version to receive support.

When asking support remember to include all relevant log files (logs are found in .minecraft/logs/), code if applicable and screenshots if possible.

Only download mods from trusted sites like CurseForge (minecraft.curseforge.com). A list of bad sites can be found here, with more information available at stopmodreposts.org

Edit your own signature at www.minecraftforge.net/forum/settings/signature/ (Make sure to check its compatibility with the Dark Theme)

Link to comment
Share on other sites

Yah they do die, but the problem isn't that they're not dying or anything - like I said it works fine and looks exactly how I want it to. The issues I'm facing are just that although each particle is tiny (data-wise), having hundreds of them generated (which happens if you place a lot of the blocks that emit them) adds up memory allocations pretty quick, which the Java GC then has to clean up (which is not good for performance).

One solution to that problem is pre-allocating stuff (object pooling, etc.) then just re-using expired/the oldest one(s). I'm just having an issue where I don't know how to do that within the Minecraft engine/Forge API. I think the solution may be for me to do a 100% completely custom rendering solution (which I'd be fine with doing) instead of hooking into the MC/Forge particle system, but I'm unsure how to do that within this environment and am either hoping for references or an alternate solution to the problem.

It also doesn't help that Java isn't my strongest suit, I'm learning the quirks of the language as I go. C++ and C# are where I know the ins and outs.

Thank you for the reply though! :D

Edited by Ilandria
Link to comment
Share on other sites

In Java it’s usually better performance wise to do create & let the GC destroy than do Object pooling just because Java was designed this way (Taken from this answer on stackoverflow). So I think that it’s ok to use Minecraft & Forge’s system. ALSO - IMO trying to optimise anything in Minecraft is a waste of time.

About Me

Spoiler

My Discord - Cadiboo#8887

My WebsiteCadiboo.github.io

My ModsCadiboo.github.io/projects

My TutorialsCadiboo.github.io/tutorials

Versions below 1.14.4 are no longer supported on this forum. Use the latest version to receive support.

When asking support remember to include all relevant log files (logs are found in .minecraft/logs/), code if applicable and screenshots if possible.

Only download mods from trusted sites like CurseForge (minecraft.curseforge.com). A list of bad sites can be found here, with more information available at stopmodreposts.org

Edit your own signature at www.minecraftforge.net/forum/settings/signature/ (Make sure to check its compatibility with the Dark Theme)

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • For crash 1, set max-tick-time to -1 in your server.properties Crash 2 shows a conflict or incompatibility between LuckPerms and the mod boh-0.0.6.1-forge-1.20.1_2.jar
    • Add the crash-report or latest.log (logs-folder) with sites like https://mclo.gs/ and paste the link to it here  
    • so my minecraft crashes when opening my world, i played without any troubles for about 5 days and today it started tweaking.. pls help me
    • Hi guys! I am having some issues with the server crashing over and over and I was hoping to get some guidance.  Thanks in advance! Crash 1: java.lang.Error: ServerHangWatchdog detected that a single server tick took 60.00 seconds (should be max 0.05)     at net.minecraft.server.dedicated.ServerWatchdog.run(ServerWatchdog.java:43) ~[server-1.20.1-20230612.114412-srg.jar%23217!/:?] {re:classloading}     at java.lang.Thread.run(Thread.java:840) ~[?:?] { Crash 2: java.lang.IllegalStateException: Capability missing for eeb7f026-34b4-42f5-9164-e7736461df83     at me.lucko.luckperms.forge.capabilities.UserCapabilityImpl.lambda$get$0(UserCapabilityImpl.java:66) ~[?:?] {re:classloading,re:classloading,re:classloading}     at net.minecraftforge.common.util.LazyOptional.orElseThrow(LazyOptional.java:261) ~[forge-1.20.1-47.3.10-universal.jar%23222!/:?] {re:mixin,re:classloading}     at me.lucko.luckperms.forge.capabilities.UserCapabilityImpl.get(UserCapabilityImpl.java:66) ~[?:?] {re:classloading,re:classloading,re:classloading}     at me.lucko.luckperms.forge.util.BrigadierInjector$InjectedPermissionRequirement.test(BrigadierInjector.java:143) ~[?:?] {}     at me.lucko.luckperms.forge.util.BrigadierInjector$InjectedPermissionRequirement.test(BrigadierInjector.java:129) ~[?:?] {}     at com.mojang.brigadier.tree.CommandNode.canUse(CommandNode.java:65) ~[brigadier-1.1.8.jar%2376!/:?] {}     at com.mojang.brigadier.CommandDispatcher.parseNodes(CommandDispatcher.java:359) ~[brigadier-1.1.8.jar%2376!/:?] {}     at com.mojang.brigadier.CommandDispatcher.parse(CommandDispatcher.java:349) ~[brigadier-1.1.8.jar%2376!/:?] {}     at com.mojang.brigadier.CommandDispatcher.parse(CommandDispatcher.java:317) ~[brigadier-1.1.8.jar%2376!/:?] {}     at net.minecraft.commands.Commands.m_230957_(Commands.java:237) ~[server-1.20.1-20230612.114412-srg.jar%23217!/:?] {re:classloading}     at net.mcreator.boh.procedures.TeleportbenProcedure.lambda$execute$2(TeleportbenProcedure.java:65) ~[boh-0.0.6.1-forge-1.20.1_2.jar%23165!/:?] {re:classloading}     at net.mcreator.boh.BohMod.lambda$tick$2(BohMod.java:96) ~[boh-0.0.6.1-forge-1.20.1_2.jar%23165!/:?] {re:classloading}     at java.util.ArrayList.forEach(ArrayList.java:1511) ~[?:?] {re:mixin}     at net.mcreator.boh.BohMod.tick(BohMod.java:96) ~[boh-0.0.6.1-forge-1.20.1_2.jar%23165!/:?] {re:classloading}     at net.mcreator.boh.__BohMod_tick_ServerTickEvent.invoke(.dynamic) ~[boh-0.0.6.1-forge-1.20.1_2.jar%23165!/:?] {re:classloading,pl:eventbus:B}     at net.minecraftforge.eventbus.ASMEventHandler.invoke(ASMEventHandler.java:73) ~[eventbus-6.0.5.jar%2352!/:?] {}     at net.minecraftforge.eventbus.EventBus.post(EventBus.java:315) ~[eventbus-6.0.5.jar%2352!/:?] {}     at net.minecraftforge.eventbus.EventBus.post(EventBus.java:296) ~[eventbus-6.0.5.jar%2352!/:?] {}     at net.minecraftforge.event.ForgeEventFactory.onPostServerTick(ForgeEventFactory.java:950) ~[forge-1.20.1-47.3.10-universal.jar%23222!/:?] {re:classloading}     at net.minecraft.server.MinecraftServer.m_5705_(MinecraftServer.java:835) ~[server-1.20.1-20230612.114412-srg.jar%23217!/:?] {re:mixin,pl:accesstransformer:B,xf:fml:xaerominimap:xaero_minecraftserver,re:classloading,pl:accesstransformer:B,xf:fml:xaerominimap:xaero_minecraftserver,pl:mixin:A}     at net.minecraft.server.MinecraftServer.m_130011_(MinecraftServer.java:661) ~[server-1.20.1-20230612.114412-srg.jar%23217!/:?] {re:mixin,pl:accesstransformer:B,xf:fml:xaerominimap:xaero_minecraftserver,re:classloading,pl:accesstransformer:B,xf:fml:xaerominimap:xaero_minecraftserver,pl:mixin:A}     at net.minecraft.server.MinecraftServer.m_206580_(MinecraftServer.java:251) ~[server-1.20.1-20230612.114412-srg.jar%23217!/:?] {re:mixin,pl:accesstransformer:B,xf:fml:xaerominimap:xaero_minecraftserver,re:classloading,pl:accesstransformer:B,xf:fml:xaerominimap:xaero_minecraftserver,pl:mixin:A}     at java.lang.Thread.run(Thread.java:840) ~[?:?] {}
    • Hello there! I am trying to make custom dimensions for a modpack I am making in an older minecraft version, 1.16.5. I like that version and it has a few other mods that have not been updated that I would still like to use. Anyway, I am having a terrible time with getting my dimension to work and have tried using code from other peoples projects to at least figure out what I'm supposed to be doing but it has not been as helpful as I would have liked. If anyone could help that would be greatly appreciated! Here is my github with all the code as I am using it: https://github.com/BladeColdsteel/InvigoratedDimensionsMod I have also included the last log, https://pastebin.com/zX9vsDSq, I had when I tried to load up a world, let me know if there is anything else I should send though, thank you!
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.