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

1.21.x Capability Factory System PR #10118

Draft
wants to merge 16 commits into
base: 1.21.x
Choose a base branch
from

Conversation

RealMangorage
Copy link
Contributor

@RealMangorage RealMangorage commented Sep 21, 2024

The goal of this PR is to provide a way for devs to register Capability Providers Factories which handle creating their providers, vs having em create it inside of an event every tick.

@RealMangorage RealMangorage marked this pull request as draft September 21, 2024 10:25
@autoforge autoforge bot added Triage This request requires the active attention of the Triage Team. Requires labelling or reviews. LTS This issue/PR is related to the current LTS version. 1.21 labels Sep 21, 2024
@autoforge autoforge bot requested a review from a team September 21, 2024 10:25
@RealMangorage RealMangorage changed the title 1.21.x Capability Factories 1.21.x Capability Factory System PR Sep 27, 2024
@RealMangorage RealMangorage marked this pull request as ready for review September 27, 2024 19:18
Copy link
Member

@Jonathing Jonathing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work so far. Before I move on with proper testing, please note the comments I've left, especially the ones in IForgeResourceKey.

Comment on lines +22 to +24
default Holder<T> getOrThrow(BlockEntity blockEntity) {
return getOrThrow(blockEntity.getLevel());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this method is a good idea. You should really be directly calling getOrThrow(Level) if you want to do it like this.

Comment on lines +26 to +28
default Holder<T> getOrThrow(Entity entity) {
return getOrThrow(entity.registryAccess());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for this. The entity's registry access method is simply calling its level reference's. Just using the single getOrThrow(Level) method is probably for the best, so as to not cause confusion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

He and I talked about this, these are meant to be helper functions that yes are strictly not necessary but people have complained that they want easier access.

Comment on lines +30 to +32
default Holder<T> getOrThrow(Level level) {
return getOrThrow(level.registryAccess());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add documentation/comment explanation to this method, similar to getOrThrow(RegistryAccess).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing license header. See existing files in project for examples.

/*
 * Copyright (c) Forge Development LLC and contributors
 * SPDX-License-Identifier: LGPL-2.1-only
 */

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing license header. See existing files in project for examples.

/*
 * Copyright (c) Forge Development LLC and contributors
 * SPDX-License-Identifier: LGPL-2.1-only
 */

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing license header. See existing files in project for examples.

/*
 * Copyright (c) Forge Development LLC and contributors
 * SPDX-License-Identifier: LGPL-2.1-only
 */

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test should clearly indicate the changes that this PR brings. So, I'd like for you to note with comments where the capability factory system is being used.

@@ -40,6 +40,7 @@
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions;
import net.minecraftforge.common.capabilities.CapabilityFactoryManager;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import, please remove.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interface extensions are not patches, so we don't need to be as careful when implementing them as we do with patches. That being said, you should still watch out for the redundancies I've noted in this file.


import net.minecraft.resources.ResourceLocation;

public interface ICapabilityFactory<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should mark this interface with @FunctionalInterface since these factories are effectively lambdas that can be used with the system individually.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more of my personal opinion, but a lot of the code in this file is hard to read. I would appreciate it if you could either add relevant JavaDoc or explanation comments to some of the methods in this. Thanks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like with CapabilityFactoryHolder, additional comments would be appreciated.

Copy link
Contributor

@PaintNinja PaintNinja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good start, but some comments.

Additionally:

  • Needs documentation written in the PR description, ideally a migration guide as well
  • Internals marking with @ApiStatus.Internal

import java.util.Map;

public final class CapabilityFactoryManager {
private static final CapabilityFactoryManager MANAGER = new CapabilityFactoryManager();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole class can be simplified by making the methods and fields static rather than requiring a level of indirection through a singleton

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya this INSTANCE field pattern is a thing from cpw's days its better to not use this indirect.
On top of that, this class needs to be annotated as internal, as the only thing needing to be public is the init function, which could potentially just be an event handler. Will need to look into that more. Point is this isn't mean to be modder facing in any way.

return MANAGER;
}

private final Map<Class<?>, Map<ResourceLocation, ICapabilityFactory<?, ?>>> factory = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maps with keys of type Class<?> should use IdentityHashMap for better performance

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you didn't mind, can you explain why that is so I know for future reference?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • HashMap uses hashCode() and equals().
  • IdentityHashMap uses identityHashCode() and ==.

By explicitly operating on reference identity rather than object equality, the internal algorithms used for the map can be tailored to take advantage of that.

In my testing with the two map types for Class<?> keys in my own EventBus, the IdentityHashMap was consistently faster than a plain HashMap.

import java.util.Map;

public class CapabilityFactoryRegisterEvent extends Event {
final Map<Class<?>, Map<ResourceLocation, ICapabilityFactory<?, ?>>> factory = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IdentityHashMap here as well


public class CapabilityProviderWithFactory<B extends ICapabilityProviderImpl<B>> implements ICapabilityProviderImpl<B> {
@VisibleForTesting
static boolean SUPPORTS_LAZY_CAPABILITIES = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fields that are not static final should not be SCREAMING_SNAKE_CASE. Either add final here or change to camelCase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was copied from CapabilityProvider

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your point being?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep existing behavior?


public CapabilityFactoryRegisterEvent() {}

public <G, H extends ICapabilityProvider> void register(Class<G> gClass, ResourceLocation resourceLocation, ICapabilityFactory<G, H> factory) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I get a better explanation/docs for the differences between these two functions?
I am not quite understanding why you have CapabilityProviderHolder as public API.
Your example code has noop listeners and singleton providers
I'll look into it a little more but we can talk about this more on discord.

@PaintNinja PaintNinja added Needs Update This request requires an update from the author to confirm whether the request is relevant. and removed Needs Update This request requires an update from the author to confirm whether the request is relevant. labels Oct 2, 2024
@PaintNinja PaintNinja marked this pull request as draft October 2, 2024 20:22
@autoforge autoforge bot added the Needs Rebase This PR requires a rebase to implement upstream changes (usually to fix merge conflicts). label Oct 26, 2024
@autoforge
Copy link

autoforge bot commented Oct 26, 2024

@RealMangorage, this pull request has conflicts, please resolve them for this PR to move forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1.21 LTS This issue/PR is related to the current LTS version. Needs Rebase This PR requires a rebase to implement upstream changes (usually to fix merge conflicts). Triage This request requires the active attention of the Triage Team. Requires labelling or reviews.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants