Skip to content

Advanced Implementations and Custom GUI Generation

JohnSmith474 edited this page Jun 13, 2026 · 33 revisions

State Listeners

Property mutations execute lifecycle broadcasts. Implement Property.Listener and bind it via addListener() to intercept state transitions.

  • onPropertyChanged(): Executes when localized disk state modifies or authoritative network synchronization completes.
  • onPropertyInvalidated(): Executes when the parent property detaches from the structural hierarchy or severs an authoritative network connection.

Detachment utilizes removeListener() to drop the active sequence.

Datapack Configuration Listeners

The framework provides pre-constructed listeners designed for configurable datapack values. To utilize these implementations, extend the target class located within johnsmith.configoverhauled.api.listener (e.g., IntegerPropertyListener, BooleanPropertyListener), implement the required Codec for deserialization, and execute the super constructor.

The following matrix maps the supported configuration property types to their designated listener classes for datapack injection and synchronization. All listener extensions are located within the johnsmith.configoverhauled.api.listener package.

Property Type Target State Type Required Listener Extension
Boolean Boolean BooleanPropertyListener
Integer Integer IntegerPropertyListener
Float Float FloatPropertyListener
Double Double DoublePropertyListener
Long Long LongPropertyListener
String String StringPropertyListener
RGBColor Integer RGBColorPropertyListener
ARGBColor Integer ARGBColorPropertyListener
Block Block BlockPropertyListener
Blocks List<Block> BlocksPropertyListener
Item Item ItemPropertyListener
Items List<Item> ItemsPropertyListener

Custom Type Definition Registration

Extend the dynamic property registry to support specialized data structures by defining a custom TypeDefinition. Execute registration during initial mod setup. Required parameters include a namespace identifier, a serialization codec, and a functional PropertyFactory to dictate state instantiation.

public static final DynamicPropertyTypeRegistry.TypeDefinition<UUID> UUID_TYPE = 
    DynamicPropertyTypeRegistry.register(
        "examplemod", 
        "uuid", 
        UUIDUtil.CODEC, 
        (name, group, def, min, max, codec)
            -> new CustomUUIDProperty(name, group, ConfigScope.LEVEL, null, (UUID) def, true)
    );

Custom Property Listener Implementation

Extend AbstractPropertyListener to inherit built-in state caching and registry resolution. This eliminates the need to manually implement baseline synchronization logic.

public class CustomUUIDPropertyListener extends AbstractPropertyListener<UUID> {
    public CustomUUIDPropertyListener(String key, UUID defaultValue) {
        super(key, defaultValue, UUIDUtil.CODEC);
    }
}

If the target data type requires value boundary validation, extend BoundedPropertyListener. This enforces upper and lower constraints prior to state application.

public class CustomBoundedIntegerListener extends BoundedPropertyListener<Integer> {
    public CustomBoundedIntegerListener(String key, Integer defaultValue, Integer min, Integer max) {
        super(key, defaultValue, min, max, Codec.INT);
    }
}

Dynamic Allocation

Refactor the instantiation parameter signature to utilize DynamicPropertyTypeRegistry.TypeDefinition instead of primitive class wrappers.

Runtime structural injection bypasses static field definition. Utilize ConfigManager::getOrCreateDynamicProperty combined with PropertyFactory to instantiate properties from deserialized network or disk definitions. Require a ConfigDescription object to map the absolute structural coordinates for the target injection.

Property<?> dynamicProp = ExampleConfig.MANAGER.getOrCreateDynamicProperty(
    description, // ConfigDescription instance defining absolute coordinates
    DynamicPropertyTypeRegistry.BOOLEAN, // Type definition
    true, // Baseline state
    null, // Lower validation boundary
    null  // Upper validation boundary
);

This operation targets datapack injection or authoritative server synchronization where structural configurations are unknown during the primary boot sequence.

Disk I/O Overrides

Standard execution automates disk synchronization upon property modification. Manual I/O execution isolates synchronization to specific scopes or shifts operations off the main thread.

  • loadScope(ConfigScope targetScope): Forces a synchronous disk read operation strictly for properties bound to the specified scope.

  • saveScope(ConfigScope targetScope): Forces a synchronous disk write operation strictly for properties bound to the specified scope.

  • saveAll(): Dispatches an asynchronous disk write operation across all configuration scopes. Returns a CompletableFuture<Void> representing the completion state of the I/O execution.

Custom Manager Allocation

Extend johnsmith.configoverhauled.impl.core.ConfigManagerImpl to intercept graphical interface generation. Override the createScreen(S parent) method to construct and return the custom screen entity.

Extend `johnsmith.configoverhauled.impl.core.ConfigManagerImpl` to intercept graphical interface generation. Override the `createScreen(S parent)` method to construct and return the specialized screen entity.

```java
import johnsmith.configoverhauled.impl.core.ConfigManagerImpl;

import org.slf4j.Logger;

public class CustomConfigManager extends ConfigManagerImpl {
    public CustomConfigManager(String modId, Logger logger) {
        super(modId, logger);
    }

    @Override
    public <S> S createScreen(S parent) {
        // Construct and return the specialized GUI
        return (S) new MyCustomScreen(parent, this);
    }
}

Bypass ConfigRegistry.getOrCreateManager(). Instantiate the custom manager directly. The ConfigManagerImpl constructor executes registry binding automatically. Enforce this allocation prior to defining structural categories or properties.

public static final CustomConfigManager MANAGER = new CustomConfigManager("examplemod", LOGGER);

Custom Screen Factories

To override default interface rendering protocols, inject a custom screen factory prior to platform-specific GUI endpoint registration.

ExampleConfig.MANAGER.setScreenFactory((parent) -> {
    return new CustomConfigScreen(parent);
});

Dynamic Widget Registry

Widget resolution is delegated to a dynamic WidgetRegistry bound to the active ConfigManager.

To inject a specialized widget resolution map overriding the default implementation:

ExampleConfig.MANAGER.setWidgetMapper(new CustomWidgetRegistry());

The active mapper bound to a specific manager instance can be resolved via:

WidgetRegistry currentMapper = ExampleConfig.MANAGER.getWidgetMapper();

GUI Implementation Pitfalls

Active configuration states are mutated via the Property.set(T newValue) accessor. Executing this method instantaneously initiates disk serialization routines. For properties assigned the LEVEL or GLOBAL operational scope, this accessor concurrently dispatches outbound network payloads to synchronize the authoritative server state.

  • Network Saturation: Text input widgets must implement rigid debouncing parameters or restrict state mutation exclusively to definitive confirmation events (e.g., loss of widget focus, terminal 'Enter' keypress). Invoking Property.set() on continuous character modification sequences triggers excessive disk I/O and immediately saturates network synchronization channels.

  • Boundary Validation: Input components must independently validate structural constraints prior to executing state mutation. Properties statically allocated with numerical boundaries (min, max) expect sanitized inputs. Pushing unvalidated or malformed states into the property accessor disrupts codec translation pipelines and induces local desynchronization from network authority. Filter invalid structural mappings strictly within the custom UI component logic.

Clone this wiki locally