Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weird generation bug when using qfapi with c2me mod, not present with fabric api (with quilt loader or fabric loader) #233

Open
offbeat-stuff opened this issue Dec 11, 2022 · 13 comments
Assignees
Labels
bug Something isn't working library: worldgen Related to the worldgen library.

Comments

@offbeat-stuff
Copy link

  • World generation is different on quilted fabric api and fabric api, when used with c2me
  • With fabric api (on quilt loader, with Simply Optimized (modrinth modpack))
    2022-12-11_16 21 42
  • With quilted fabric api (on quilt loader, with Simply Optimized (modrinth modpack))
    2022-12-11_16 23 09
    *World Spawn point for same seed
@LambdAurora
Copy link
Contributor

I don't know if I even want to question what in hell happened here.

(I lied, I think I know what happened)

It looks like all the grass/dirt went missing! Which can only indicate one thing: surface rules.
QSL has a unique API compared to Fabric API which is the surface rules API which allows to add and modify those.
My guess is C2ME being the nightmare it is doesn't support that with its aggressive multithreading.

I don't think it's for us to fix.

@HyperSoop
Copy link

Shouldn't QFAPI keep parity with FAPI though? Is there a reason behind this uniqueness?

@LambdAurora
Copy link
Contributor

Please re-read my comment above: QSL has an API allowing for modification of surface rules. C2ME being a multithreading nightmare breaks such API.

@HyperSoop
Copy link

It might be a misconception on my end, but isn't QSL/QFAPI supposed to work with mods intended for Fabric just as FAPI would

@PhantomG27249
Copy link

PhantomG27249 commented Dec 13, 2022

The bug has been narrowed to an allocation optimization in c2me that is incompatible with the surface API. It has nothing to do with threading fortunately but I guess the surface API doesn't play nice with this one.

Mixin in question:
https://github.com/RelativityMC/C2ME-fabric/blob/ver/1.19.3/c2me-opts-allocs/src/main/java/com/ishland/c2me/opts/allocs/mixin/surfacebuilder/MixinMaterialRulesSequenceMaterialRule.java

The easiest fix would probably be to probably disable the opt when loading with qfapi, alternatively, it might be possible for us to cache the surface API rules or something along those lines.

@LambdAurora
Copy link
Contributor

Or alternatively we can investigate such optimization on our side and look if it can be implemented neatly in sync with the API, making breakability lower (if the same optimization is disabled in C2ME), could also reduce maintainability cost of the compatibility

@PhantomG27249
Copy link

If you guys want to implement such an optimization that would be great tbh because it would mean c2me doesn't lose perf when running with quilt and also makes compatibility less of a headache.

@LambdAurora
Copy link
Contributor

I have to wonder, has any measuring been done on this code to measure the performance improvements?

@PhantomG27249
Copy link

PhantomG27249 commented Dec 13, 2022

I'll check with ishland when they are back this weekend, that opt has been there for a while and worked fine with qsl 1.19.2. A lot of the caching opts in c2me are to reduce the allocation rate of world gen which becomes somewhat unmanageable when threading becomes involved otherwise. Though tbh world gen in general benefits from allocation reductions so non thread cases might see gains too. Usually though allocation optimization is done based on profiling with jprofiler

@LambdAurora
Copy link
Contributor

Yeah, talking to one of our worldgen expert and we're actually quite confused by the optimization:

Mojang has something in the lines of

SurfaceRules.SurfaceRule apply(SurfaceRules.Context context) {
	if (this.sequence.size() == 1) {
		return this.sequence.get(0).apply(context);
	} else {
		var builder = ImmutableList.<SurfaceRule>builder();
		
		for (var rule : this.sequence) {
			builder.add(rule.apply(context));
		}
		
		return new SequenceRule(builder.build());
	}
}

So we can see this as this diff:

SurfaceRules.SurfaceRule apply(SurfaceRules.Context context) {
-	if (this.sequence.size() == 1) {
-		return this.sequence.get(0).apply(context);
+	if (this.isSingleOrNoElement) {
+		return this.firstElement != null ? this.firstElement.apply(context) : EMPTY;
	} else {
-		var builder = ImmutableList.<SurfaceRule>builder();
+		var builder = ImmutableList.<SurfaceRule>builderWithExpectedSize(this.sequenceArray.length);
		
-		for (var rule : this.sequence) { // Iterator allocation.
+		for (var rule : this.sequenceArray) { // Compiles to iteration using an increment local.
			builder.add(rule.apply(context));
		}
		
		return new SequenceRule(builder.build());
	}
}

It is notable that checking for an empty list doesn't make much sense: the overload method explicitly disallows for this to happen and should be treat as a pre-condition of the record. Though it's what's causing this conflict to not actually crash and just generate a strange wasteland of stone.

The difference also explains why the conflict happens: you instantiate an array at the instantiation of the class and copy the content of the list, except we take a different approach: we create a root sequence rule that is populated after its creation, causing this bug in the first place.
This detail is, iirc, very needed for the API to work properly and have a simple and yet powerful implementation in 1.19.2. While in 1.19.3 this got simplified a lot and does not require post modification, we probably could ditch this implementation detail entirely.

I'm quite interested in the builderWithExpectedSize tbh, it sounds very interesting and might be worth looking into ourselves?
As for the array vs iterator, is something I definitely would like to get some data on.

@LambdAurora LambdAurora transferred this issue from QuiltMC/quilted-fabric-api Dec 13, 2022
@LambdAurora LambdAurora added bug Something isn't working library: worldgen Related to the worldgen library. labels Dec 13, 2022
@LambdAurora LambdAurora self-assigned this Dec 13, 2022
@duplexsystem
Copy link

This issue has been worked around on the C2ME side.

@ishland
Copy link

ishland commented Dec 18, 2022

Some raw benchmark data regarding list iteration vs array iteration and builder vs builderWithExpectedSize:
https://gist.github.com/ishland/9b0a5b6f29727c8f2b460d377bacf9d6

@LambdAurora
Copy link
Contributor

Wow, I was expecting something in those lines, glad to see I guessed it right!

1.19.3 definitely can use the array iteration optimization in theory, though I'm still slightly scared of the possibility of some people passing a mutable list.
And for the builder one every version of QSL with this API can implement it.

Will look into it when I have time, thank you for the benchmarks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working library: worldgen Related to the worldgen library.
Projects
None yet
Development

No branches or pull requests

6 participants