diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockState.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockState.java index 9e894d9cc0..cb94239af9 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockState.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockState.java @@ -19,23 +19,12 @@ package com.sk89q.worldedit.world.block; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import com.google.common.collect.Table; -import com.google.common.collect.Tables; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.extension.platform.Capability; -import com.sk89q.worldedit.extension.platform.Watchdog; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.concurrency.LazyReference; -import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; import org.enginehub.linbus.tree.LinCompoundTag; -import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -62,132 +51,24 @@ public void setInternalId(BlockState blockState, int internalId) { private final BlockType blockType; private final Map, Object> values; + private final int stateListIndex; private final BaseBlock emptyBaseBlock; private final LazyReference lazyStringRepresentation; - // Neighbouring state table. - private Table, Object, BlockState> states; - /** * The internal ID of the block state. */ private volatile int internalId = BlockStateIdAccess.invalidId(); - BlockState(BlockType blockType) { - this(blockType, Collections.emptyMap()); - } - - BlockState(BlockType blockType, Map, Object> values) { + BlockState(BlockType blockType, Map, Object> values, int stateListIndex) { this.blockType = blockType; this.values = values; + this.stateListIndex = stateListIndex; this.emptyBaseBlock = new BaseBlock(this); this.lazyStringRepresentation = LazyReference.from(BlockStateHolder.super::getAsString); } - /** - * Generates a map of all possible states for a block type. - * - * @param blockType The block type - * @return The map of states - */ - static Map, Object>, BlockState> generateStateMap(BlockType blockType) { - List> properties = blockType.getProperties(); - ImmutableMap.Builder, Object>, BlockState> stateMapBuilder = null; - - if (!properties.isEmpty()) { - // Create a list of lists of values, with a copy of the underlying lists - List> separatedValues = Lists.newArrayListWithCapacity(properties.size()); - for (Property prop : properties) { - separatedValues.add(ImmutableList.copyOf(prop.values())); - } - - List> valueLists = Lists.cartesianProduct(separatedValues); - stateMapBuilder = ImmutableMap.builderWithExpectedSize(valueLists.size()); - for (List valueList : valueLists) { - int valueCount = valueList.size(); - Map, Object> valueMap = new Reference2ObjectArrayMap<>(valueCount); - for (int i = 0; i < valueCount; i++) { - Property property = properties.get(i); - Object value = valueList.get(i); - valueMap.put(property, value); - } - valueMap = Collections.unmodifiableMap(valueMap); - stateMapBuilder.put(valueMap, new BlockState(blockType, valueMap)); - } - } - - ImmutableMap, Object>, BlockState> stateMap; - - if (stateMapBuilder == null) { - // No properties. - stateMap = ImmutableMap.of(ImmutableMap.of(), new BlockState(blockType)); - } else { - stateMap = stateMapBuilder.build(); - } - - Watchdog watchdog = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS) - .getWatchdog(); - long startTime = System.currentTimeMillis(); - - for (BlockState state : stateMap.values()) { - state.populate(stateMap); - - // Sometimes loading can take a while. This is the perfect spot to let MC know we're working. - if (watchdog != null) { - watchdog.tick(); - } - } - long timeTaken = System.currentTimeMillis() - startTime; - if (timeTaken > 5000) { - WorldEdit.logger.warn("Took more than 5 seconds to generate complete state map for " + blockType.id() + ". This block is likely improperly using properties. State count: " + stateMap.size() + ". " + timeTaken + "ms elapsed."); - } - - return stateMap; - } - - /** - * Creates the underlying state table for object lookups. - * - * @param stateMap The state map to generate the table from - */ - private void populate(Map, Object>, BlockState> stateMap) { - Table, Object, BlockState> table = Tables.newCustomTable( - new Reference2ObjectArrayMap<>(this.values.size()), - Reference2ObjectArrayMap::new - ); - - for (final Map.Entry, Object> entry : this.values.entrySet()) { - final Property property = (Property) entry.getKey(); - - for (Object value : property.values()) { - if (value != entry.getValue()) { - BlockState modifiedState = stateMap.get(this.withValue(property, value)); - if (modifiedState != null) { - table.put(property, value, modifiedState); - } else { - WorldEdit.logger.warn(stateMap); - WorldEdit.logger.warn("Found a null state at " + this.withValue(property, value)); - } - } - } - } - - this.states = Tables.unmodifiableTable(table); - } - - private Map, Object> withValue(final Property property, final V value) { - final Map, Object> values = new Reference2ObjectArrayMap<>(this.values.size()); - for (Map.Entry, Object> entry : this.values.entrySet()) { - if (entry.getKey().equals(property)) { - values.put(entry.getKey(), value); - } else { - values.put(entry.getKey(), entry.getValue()); - } - } - return Collections.unmodifiableMap(values); - } - @Override public BlockType getBlockType() { return this.blockType; @@ -195,8 +76,21 @@ public BlockType getBlockType() { @Override public BlockState with(final Property property, final V value) { - BlockState result = states.get(property, value); - return result == null ? this : result; + if (this.stateListIndex == -1) { + return this; + } + Object currentValue = this.values.get(property); + if (Objects.equals(currentValue, value)) { + return this; + } + + int newIndex = blockType.getInternalStateList().updateIndexOrInvalid( + this.stateListIndex, property, currentValue, value + ); + if (newIndex == -1) { + return this; + } + return blockType.getInternalStateList().get(newIndex); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java index c2f4794d16..087ce032e6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java @@ -20,7 +20,6 @@ package com.sk89q.worldedit.world.block; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.registry.Keyed; @@ -32,9 +31,10 @@ import com.sk89q.worldedit.world.item.ItemTypes; import com.sk89q.worldedit.world.registry.BlockMaterial; import com.sk89q.worldedit.world.registry.LegacyMapper; -import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -46,25 +46,34 @@ public class BlockType implements Keyed { public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("block type", "block_type", "minecraft", true); + private static Map> computeProperties(BlockType self) { + Map> propertiesMap = WorldEdit.getInstance().getPlatformManager() + .queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry().getProperties(self); + String[] propertyNames = propertiesMap.keySet().toArray(new String[0]); + Arrays.sort(propertyNames); + Object[] properties = new Object[propertyNames.length]; + for (int i = 0; i < propertyNames.length; i++) { + properties[i] = propertiesMap.get(propertyNames[i]); + } + return Object2ObjectMaps.unmodifiable(new Object2ObjectArrayMap<>(propertyNames, properties)); + } + private final String id; - private final Function values; - private final LazyReference defaultState - = LazyReference.from(this::computeDefaultState); + private final LazyReference defaultState; @SuppressWarnings("this-escape") private final LazyReference emptyFuzzy = LazyReference.from(() -> new FuzzyBlockState(this)); @SuppressWarnings("this-escape") private final LazyReference>> properties - = LazyReference.from(this::computeProperties); + = LazyReference.from(() -> computeProperties(this)); + @SuppressWarnings("this-escape") + private final LazyReference internalStateList = + LazyReference.from(() -> BlockTypeStateList.createFor(this)); @SuppressWarnings("this-escape") private final LazyReference blockMaterial = LazyReference.from(() -> WorldEdit.getInstance().getPlatformManager() .queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry().getMaterial(this)); @SuppressWarnings("this-escape") - private final LazyReference, Object>, BlockState>> blockStatesMap - = LazyReference.from(() -> BlockState.generateStateMap(this)); - - @SuppressWarnings("this-escape") @Deprecated private final LazyReference name = LazyReference.from(() -> WorldEdit.getInstance().getPlatformManager() .queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry().getName(this)); @@ -77,36 +86,25 @@ public BlockType(String id) { this(id, null); } - public BlockType(String id, Function values) { + public BlockType(String id, Function applyDefaultValues) { // If it has no namespace, assume minecraft. if (!id.contains(":")) { id = "minecraft:" + id; } this.id = id; - this.values = values; + this.defaultState = LazyReference.from(() -> computeDefaultState(applyDefaultValues)); } - private Map> computeProperties() { - var propertiesMap = WorldEdit.getInstance().getPlatformManager() - .queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry().getProperties(this); - List sortedPropertyNames = propertiesMap.keySet().stream().sorted().toList(); - Map> sortedPropertiesMap = new Reference2ObjectArrayMap<>(propertiesMap.size()); - for (String propertyName : sortedPropertyNames) { - sortedPropertiesMap.put(propertyName, propertiesMap.get(propertyName)); - } - return Collections.unmodifiableMap(sortedPropertiesMap); + BlockTypeStateList getInternalStateList() { + return internalStateList.getValue(); } - private BlockState computeDefaultState() { - BlockState defaultState = Iterables.getFirst(getBlockStatesMap().values(), null); - if (values != null) { - defaultState = values.apply(defaultState); + private BlockState computeDefaultState(Function applyDefaultValues) { + BlockState state = getInternalStateList().getFirst(); + if (applyDefaultValues != null) { + state = applyDefaultValues.apply(state); } - return defaultState; - } - - private Map, Object>, BlockState> getBlockStatesMap() { - return blockStatesMap.getValue(); + return state; } /** @@ -190,7 +188,7 @@ public FuzzyBlockState getFuzzyMatcher() { * @return All possible states */ public List getAllStates() { - return ImmutableList.copyOf(getBlockStatesMap().values()); + return getInternalStateList(); } /** @@ -199,9 +197,9 @@ public List getAllStates() { * @return The state, if it exists */ public BlockState getState(Map, Object> key) { - BlockState state = getBlockStatesMap().get(key); - checkArgument(state != null, "%s has no state for %s", this, key); - return state; + BlockTypeStateList map = getInternalStateList(); + int index = map.calculateIndex(key); + return map.get(index); } /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypeStateList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypeStateList.java new file mode 100644 index 0000000000..1ad9b778e9 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypeStateList.java @@ -0,0 +1,62 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.world.block; + +import com.google.errorprone.annotations.Immutable; +import com.sk89q.worldedit.registry.state.Property; +import it.unimi.dsi.fastutil.objects.AbstractObjectList; + +import java.util.Collections; +import java.util.Map; + +/** + * A specialized list for looking up block states from a block type. + */ +// Using AbstractObjectList over AbstractList since we don't need the modCount functionality +@Immutable +abstract class BlockTypeStateList extends AbstractObjectList { + static BlockTypeStateList createFor(BlockType blockType) { + if (blockType.getProperties().isEmpty()) { + // Special case, we have only one state: the default state + return new SingletonBlockTypeStateList(new BlockState(blockType, Collections.emptyMap(), 0)); + } + return new DefaultBlockTypeStateList(blockType); + } + + /** + * Calculates the index in the states array for the given property values. + * This can later be used to perform fast lookups by replacing only a specific property. + * + * @param state the map of property values + * @return the index in the states + */ + public abstract int calculateIndex(Map, ?> state); + + /** + * Updates the current index by changing a single property's value. + * + * @param currentIndex the current index + * @param property the property to change + * @param oldValue the old value + * @param newValue the new value + * @return the updated index, or {@code -1} if the property or value is invalid + */ + public abstract int updateIndexOrInvalid(int currentIndex, Property property, Object oldValue, Object newValue); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/DefaultBlockTypeStateList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/DefaultBlockTypeStateList.java new file mode 100644 index 0000000000..54512dc6fc --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/DefaultBlockTypeStateList.java @@ -0,0 +1,213 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.world.block; + +import com.google.common.base.Verify; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import com.sk89q.worldedit.registry.state.Property; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +// Suppress Immutable: Properly annotating BlockState with Immutable is for the future +@SuppressWarnings("Immutable") +final class DefaultBlockTypeStateList extends BlockTypeStateList { + /** + * Pre-calculated information about a property. + * + * @param property the property + * @param values the possible values, as an immutable list with reference equality semantics + * @param stride the stride for this property in the state index calculation, i.e. + * how many states are represented by earlier properties in the list that we must skip for each index increment + */ + @Immutable + // Suppress Immutable: Properly annotating Property with Immutable is for the future + @SuppressWarnings("Immutable") + private record PropertyEntry( + Property property, + ImmutableList values, + int stride + ) { + int getOffsetForValueOrInvalid(Object value) { + int valueIndex = values.indexOf(value); + return valueIndex == -1 ? -1 : valueIndex * stride; + } + } + + /* + This class assembles all block states into a single array. + The order of the states is such that all the first property's values are iterated first, + then the second property's values, and so on. + This allows for fast index calculation by multiplying the value index by the stride for each property. + For example, given [color: [red, green], shape: [cube, sphere, pyramid]]: + The states would be ordered as: + 0: color=red, shape=cube (0 + 0*2) + 1: color=green, shape=cube (1 + 0*2) + 2: color=red, shape=sphere (0 + 1*2) + 3: color=green, shape=sphere (1 + 1*2) + 4: color=red, shape=pyramid (0 + 2*2) + 5: color=green, shape=pyramid (1 + 2*2) + */ + + private final ImmutableList propertyEntries; + private final ImmutableList states; + + DefaultBlockTypeStateList(BlockType blockType) { + Collection> properties = blockType.getProperties(); + ImmutableList.Builder propertyEntriesBuilder = + ImmutableList.builderWithExpectedSize(properties.size()); + int nextStride = 1; + for (Property property : properties) { + propertyEntriesBuilder.add(new PropertyEntry( + property, + ImmutableList.copyOf(property.values()), + nextStride + )); + nextStride = Math.multiplyExact(nextStride, property.values().size()); + } + this.propertyEntries = propertyEntriesBuilder.build(); + this.states = createStates(nextStride, blockType); + } + + private ImmutableList createStates(int totalStates, BlockType blockType) { + int[] propertyValueCounts = new int[propertyEntries.size()]; + + // We can share the propsArray across all states since it will never differ + Property[] propsArray = new Property[propertyEntries.size()]; + for (int i = 0; i < propsArray.length; i++) { + propsArray[i] = propertyEntries.get(i).property; + } + + // Values array caches the current values for each property + Object[] valuesArray = new Object[propertyEntries.size()]; + for (int i = 0; i < valuesArray.length; i++) { + valuesArray[i] = propertyEntries.get(i).values.getFirst(); + } + + ImmutableList.Builder statesBuilder = ImmutableList.builderWithExpectedSize(totalStates); + for (int i = 0; i < totalStates; i++) { + // Create the BlockState + statesBuilder.add(new BlockState( + blockType, + Object2ObjectMaps.unmodifiable( + // No need to clone propsArray, but we need a new valuesArray each time + new Object2ObjectArrayMap<>(propsArray, valuesArray.clone()) + ), + i + )); + + if (i + 1 >= totalStates) { + break; + } + prepareNextValueInSlot(propertyValueCounts, valuesArray, 0); + } + return statesBuilder.build(); + } + + private void prepareNextValueInSlot(int[] propertyValueCounts, Object[] valuesArray, int index) { + PropertyEntry entry = propertyEntries.get(index); + propertyValueCounts[index]++; + if (propertyValueCounts[index] >= entry.values.size()) { + // Reset this property and increment the next one + propertyValueCounts[index] = 0; + valuesArray[index] = entry.values.getFirst(); + Verify.verify(index + 1 < propertyValueCounts.length, "Tried to increment past last property"); + prepareNextValueInSlot(propertyValueCounts, valuesArray, index + 1); + } else { + // Set the next value for this property + valuesArray[index] = entry.values.get(propertyValueCounts[index]); + } + } + + @Override + public int size() { + return states.size(); + } + + @Override + public BlockState get(int index) { + return states.get(index); + } + + @Override + public int calculateIndex(Map, ?> state) { + if (state.size() != propertyEntries.size()) { + throw new IllegalArgumentException(getDetailedPropertyMismatchException(state)); + } + int index = 0; + for (PropertyEntry entry : propertyEntries) { + Object value = state.get(entry.property); + if (value == null) { + if (!state.containsKey(entry.property)) { + throw new IllegalArgumentException(getDetailedPropertyMismatchException(state)); + } + throw new IllegalArgumentException("Null value for property " + entry.property.name()); + } + int offset = entry.getOffsetForValueOrInvalid(value); + if (offset == -1) { + throw new IllegalArgumentException("Invalid value for property " + entry.property.name() + ": " + value); + } + index += offset; + } + return index; + } + + private String getDetailedPropertyMismatchException(Map, ?> state) { + // Spend some time computing the incorrect properties for better error messages + Set> missingProperties = propertyEntries.stream() + .map(e -> e.property) + .filter(p -> !state.containsKey(p)) + .collect(Collectors.toSet()); + Set> extraProperties = state.keySet().stream() + .filter(p -> propertyEntries.stream().noneMatch(e -> e.property == p)) + .collect(Collectors.toSet()); + StringBuilder errorMessage = new StringBuilder("State has incorrect number of properties."); + if (!missingProperties.isEmpty()) { + errorMessage.append(" Missing properties: ").append(missingProperties).append("."); + } + if (!extraProperties.isEmpty()) { + errorMessage.append(" Extra properties: ").append(extraProperties).append("."); + } + return errorMessage.toString(); + } + + @Override + public int updateIndexOrInvalid(int currentIndex, Property property, Object oldValue, Object newValue) { + if (currentIndex < 0 || currentIndex >= size()) { + return -1; + } + for (PropertyEntry entry : propertyEntries) { + if (entry.property == property) { + int oldOffset = entry.getOffsetForValueOrInvalid(oldValue); + int newOffset = entry.getOffsetForValueOrInvalid(newValue); + if (oldOffset == -1 || newOffset == -1) { + return -1; + } + return currentIndex - oldOffset + newOffset; + } + } + return -1; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/FuzzyBlockState.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/FuzzyBlockState.java index 105bd19779..4476084763 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/FuzzyBlockState.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/FuzzyBlockState.java @@ -37,11 +37,11 @@ public class FuzzyBlockState extends BlockState { FuzzyBlockState(BlockType blockType) { - super(blockType); + super(blockType, Collections.emptyMap(), -1); } private FuzzyBlockState(BlockType blockType, Map, Object> values) { - super(blockType, Collections.unmodifiableMap(values)); + super(blockType, Collections.unmodifiableMap(values), -1); } /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/SingletonBlockTypeStateList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/SingletonBlockTypeStateList.java new file mode 100644 index 0000000000..dc49139d68 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/SingletonBlockTypeStateList.java @@ -0,0 +1,62 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.world.block; + +import com.google.common.base.Preconditions; +import com.sk89q.worldedit.registry.state.Property; + +import java.util.Map; + +/** + * {@link BlockTypeStateList} with only one possible state. + */ +// Suppress Immutable: Properly annotating BlockState with Immutable is for the future +@SuppressWarnings("Immutable") +final class SingletonBlockTypeStateList extends BlockTypeStateList { + private final BlockState state; + + SingletonBlockTypeStateList(BlockState state) { + this.state = state; + } + + @Override + public BlockState get(int index) { + Preconditions.checkElementIndex(index, 1); + return state; + } + + @Override + public int size() { + return 1; + } + + @Override + public int calculateIndex(Map, ?> state) { + if (!state.isEmpty()) { + throw new IllegalArgumentException("No properties expected for singleton state"); + } + return 0; + } + + @Override + public int updateIndexOrInvalid(int currentIndex, Property property, Object oldValue, Object newValue) { + return -1; + } +}