Replace guava caches with caffeine

This commit is contained in:
Luck
2017-04-03 00:53:04 +01:00
Unverified
parent e7e2e3f7e0
commit 2749563f5d
23 changed files with 228 additions and 394 deletions
@@ -169,6 +169,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
localeManager = new NoopLocaleManager();
senderFactory = new SpongeSenderFactory(this);
log = new LoggerImpl(getConsoleSender());
LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this);
verboseHandler = new VerboseHandler(scheduler.getAsyncExecutor(), getVersion());
permissionVault = new PermissionVault(scheduler.getAsyncExecutor());
@@ -22,15 +22,13 @@
package me.lucko.luckperms.sponge.managers;
import lombok.Getter;
import lombok.NonNull;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.UncheckedExecutionException;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.context.ContextSet;
@@ -53,13 +51,14 @@ import co.aikar.timings.Timing;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class SpongeGroupManager implements GroupManager, LPSubjectCollection {
@Getter
private final LPSpongePlugin plugin;
private final LoadingCache<String, SpongeGroup> objects = CacheBuilder.newBuilder()
private final LoadingCache<String, SpongeGroup> objects = Caffeine.newBuilder()
.build(new CacheLoader<String, SpongeGroup>() {
@Override
public SpongeGroup load(String i) {
@@ -67,31 +66,28 @@ public class SpongeGroupManager implements GroupManager, LPSubjectCollection {
}
@Override
public ListenableFuture<SpongeGroup> reload(String i, SpongeGroup t) {
return Futures.immediateFuture(t); // Never needs to be refreshed.
public SpongeGroup reload(String i, SpongeGroup t) {
return t; // Never needs to be refreshed.
}
});
private final LoadingCache<String, LPSubject> subjectLoadingCache = CacheBuilder.newBuilder()
private final LoadingCache<String, LPSubject> subjectLoadingCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader<String, LPSubject>() {
@Override
public LPSubject load(String s) throws Exception {
if (isLoaded(s)) {
return getIfLoaded(s).getSpongeData();
}
// Request load
plugin.getStorage().createAndLoadGroup(s, CreationCause.INTERNAL).join();
SpongeGroup group = getIfLoaded(s);
if (group == null) {
plugin.getLog().severe("Error whilst loading group '" + s + "'.");
throw new RuntimeException();
}
return group.getSpongeData();
.build(s -> {
if (isLoaded(s)) {
return getIfLoaded(s).getSpongeData();
}
// Request load
getPlugin().getStorage().createAndLoadGroup(s, CreationCause.INTERNAL).join();
SpongeGroup group = getIfLoaded(s);
if (group == null) {
getPlugin().getLog().severe("Error whilst loading group '" + s + "'.");
throw new RuntimeException();
}
return group.getSpongeData();
});
public SpongeGroupManager(LPSpongePlugin plugin) {
@@ -114,7 +110,7 @@ public class SpongeGroupManager implements GroupManager, LPSubjectCollection {
@Override
public SpongeGroup getOrMake(String id) {
return objects.getUnchecked(id.toLowerCase());
return objects.get(id.toLowerCase());
}
@Override
@@ -173,7 +169,7 @@ public class SpongeGroupManager implements GroupManager, LPSubjectCollection {
try {
return subjectLoadingCache.get(id);
} catch (UncheckedExecutionException | ExecutionException e) {
} catch (Exception e) {
e.printStackTrace();
plugin.getLog().warn("Couldn't get group subject for id: " + id);
return plugin.getService().getFallbackGroupSubjects().get(id); // fallback to the transient collection
@@ -22,15 +22,13 @@
package me.lucko.luckperms.sponge.managers;
import lombok.Getter;
import lombok.NonNull;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.UncheckedExecutionException;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.context.ContextSet;
@@ -57,13 +55,14 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class SpongeUserManager implements UserManager, LPSubjectCollection {
@Getter
private final LPSpongePlugin plugin;
private final LoadingCache<UserIdentifier, SpongeUser> objects = CacheBuilder.newBuilder()
private final LoadingCache<UserIdentifier, SpongeUser> objects = Caffeine.newBuilder()
.build(new CacheLoader<UserIdentifier, SpongeUser>() {
@Override
public SpongeUser load(UserIdentifier i) {
@@ -71,43 +70,40 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection {
}
@Override
public ListenableFuture<SpongeUser> reload(UserIdentifier i, SpongeUser t) {
return Futures.immediateFuture(t); // Never needs to be refreshed.
public SpongeUser reload(UserIdentifier i, SpongeUser t) {
return t; // Never needs to be refreshed.
}
});
private final LoadingCache<UUID, LPSubject> subjectLoadingCache = CacheBuilder.newBuilder()
private final LoadingCache<UUID, LPSubject> subjectLoadingCache = Caffeine.<UUID, LPSubject>newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader<UUID, LPSubject>() {
@Override
public LPSubject load(UUID u) throws Exception {
if (isLoaded(UserIdentifier.of(u, null))) {
SpongeUser user = get(u);
if (user.getUserData() == null) {
user.setupData(false);
}
return get(u).getSpongeData();
}
// Request load
plugin.getStorage().loadUser(u, "null").join();
.build(u -> {
if (isLoaded(UserIdentifier.of(u, null))) {
SpongeUser user = get(u);
if (user == null) {
plugin.getLog().severe("Error whilst loading user '" + u + "'.");
throw new RuntimeException();
}
user.setupData(false);
if (user.getUserData() == null) {
plugin.getLog().warn("User data not present for requested user id: " + u);
user.setupData(false);
}
return user.getSpongeData();
return get(u).getSpongeData();
}
// Request load
getPlugin().getStorage().loadUser(u, "null").join();
SpongeUser user = get(u);
if (user == null) {
getPlugin().getLog().severe("Error whilst loading user '" + u + "'.");
throw new RuntimeException();
}
user.setupData(false);
if (user.getUserData() == null) {
getPlugin().getLog().warn("User data not present for requested user id: " + u);
}
return user.getSpongeData();
});
public SpongeUserManager(LPSpongePlugin plugin) {
@@ -144,7 +140,7 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection {
@Override
public SpongeUser getOrMake(UserIdentifier id) {
return objects.getUnchecked(id);
return objects.get(id);
}
@Override
@@ -251,7 +247,7 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection {
UUID u = plugin.getUuidCache().getUUID(uuid);
try {
return subjectLoadingCache.get(u);
} catch (UncheckedExecutionException | ExecutionException e) {
} catch (Exception e) {
e.printStackTrace();
plugin.getLog().warn("Couldn't get user subject for id: " + id);
return plugin.getService().getFallbackUserSubjects().get(id); // fallback to the transient collection
@@ -24,9 +24,8 @@ package me.lucko.luckperms.sponge.model;
import lombok.Getter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.api.LocalizedNode;
@@ -68,7 +67,11 @@ public class SpongeGroup extends Group {
}
public static class GroupSubject implements LPSubject {
@Getter
private final SpongeGroup parent;
@Getter
private final LPSpongePlugin plugin;
@Getter
@@ -77,38 +80,32 @@ public class SpongeGroup extends Group {
@Getter
private final LuckPermsSubjectData transientSubjectData;
private final LoadingCache<ContextSet, NodeTree> permissionCache = CacheBuilder.newBuilder()
private final LoadingCache<ContextSet, NodeTree> permissionCache = Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(new CacheLoader<ContextSet, NodeTree>() {
@Override
public NodeTree load(ContextSet contexts) {
// TODO move this away from NodeTree
Map<String, Boolean> permissions = parent.getAllNodes(ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))).stream()
.map(LocalizedNode::getNode)
.collect(Collectors.toMap(Node::getPermission, Node::getValue));
.build(contexts -> {
// TODO move this away from NodeTree
Map<String, Boolean> permissions = getParent().getAllNodes(ExtractedContexts.generate(getPlugin().getService().calculateContexts(contexts))).stream()
.map(LocalizedNode::getNode)
.collect(Collectors.toMap(Node::getPermission, Node::getValue));
return NodeTree.of(permissions);
}
return NodeTree.of(permissions);
});
private final LoadingCache<ContextSet, Set<SubjectReference>> parentCache = CacheBuilder.newBuilder()
private final LoadingCache<ContextSet, Set<SubjectReference>> parentCache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<ContextSet, Set<SubjectReference>>() {
@Override
public Set<SubjectReference> load(ContextSet contexts) {
Set<SubjectReference> subjects = parent.getAllNodes(ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))).stream()
.map(LocalizedNode::getNode)
.filter(Node::isGroupNode)
.map(Node::getGroupName)
.map(s -> plugin.getService().getGroupSubjects().get(s))
.map(LPSubject::toReference)
.collect(Collectors.toSet());
.build(contexts -> {
Set<SubjectReference> subjects = getParent().getAllNodes(ExtractedContexts.generate(getPlugin().getService().calculateContexts(contexts))).stream()
.map(LocalizedNode::getNode)
.filter(Node::isGroupNode)
.map(Node::getGroupName)
.map(s -> getPlugin().getService().getGroupSubjects().get(s))
.map(LPSubject::toReference)
.collect(Collectors.toSet());
subjects.addAll(plugin.getService().getGroupSubjects().getDefaultSubject().resolve(getService()).getParents(contexts));
subjects.addAll(plugin.getService().getDefaults().getParents(contexts));
subjects.addAll(getPlugin().getService().getGroupSubjects().getDefaultSubject().resolve(getService()).getParents(contexts));
subjects.addAll(getPlugin().getService().getDefaults().getParents(contexts));
return ImmutableSet.copyOf(subjects);
}
return ImmutableSet.copyOf(subjects);
});
private GroupSubject(LPSpongePlugin plugin, SpongeGroup parent) {
@@ -159,7 +156,7 @@ public class SpongeGroup extends Group {
@Override
public Tristate getPermissionValue(ContextSet contexts, String permission) {
try (Timing ignored = plugin.getTimings().time(LPTiming.GROUP_GET_PERMISSION_VALUE)) {
NodeTree nt = permissionCache.getUnchecked(contexts);
NodeTree nt = permissionCache.get(contexts);
Tristate t = Util.convertTristate(nt.get(permission));
if (t != Tristate.UNDEFINED) {
return t;
@@ -185,7 +182,7 @@ public class SpongeGroup extends Group {
@Override
public Set<SubjectReference> getParents(ContextSet contexts) {
try (Timing ignored = plugin.getTimings().time(LPTiming.GROUP_GET_PARENTS)) {
return parentCache.getUnchecked(contexts);
return parentCache.get(contexts);
}
}
@@ -30,15 +30,13 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapMaker;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Tristate;
@@ -109,7 +107,7 @@ public class LuckPermsService implements PermissionService {
private final Set<CalculatedSubjectData> localDataCaches;
@Getter(value = AccessLevel.NONE)
private final LoadingCache<String, LPSubjectCollection> collections = CacheBuilder.newBuilder()
private final LoadingCache<String, LPSubjectCollection> collections = Caffeine.newBuilder()
.build(new CacheLoader<String, LPSubjectCollection>() {
@Override
public LPSubjectCollection load(String s) {
@@ -117,8 +115,8 @@ public class LuckPermsService implements PermissionService {
}
@Override
public ListenableFuture<LPSubjectCollection> reload(String s, LPSubjectCollection collection) {
return Futures.immediateFuture(collection); // Never needs to be refreshed.
public LPSubjectCollection reload(String s, LPSubjectCollection collection) {
return collection; // Never needs to be refreshed.
}
});
@@ -171,7 +169,7 @@ public class LuckPermsService implements PermissionService {
@Override
public LPSubjectCollection getSubjects(String s) {
try (Timing ignored = plugin.getTimings().time(LPTiming.GET_SUBJECTS)) {
return collections.getUnchecked(s.toLowerCase());
return collections.get(s.toLowerCase());
}
}
@@ -25,9 +25,9 @@ package me.lucko.luckperms.sponge.service.calculated;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -103,7 +103,7 @@ public class CalculatedSubjectData implements LPSubjectData {
private final String calculatorDisplayName;
private final Map<ContextSet, Map<String, Boolean>> permissions = new ConcurrentHashMap<>();
private final LoadingCache<ContextSet, CalculatorHolder> permissionCache = CacheBuilder.newBuilder()
private final LoadingCache<ContextSet, CalculatorHolder> permissionCache = Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(new CacheLoader<ContextSet, CalculatorHolder>() {
@Override
@@ -131,7 +131,7 @@ public class CalculatedSubjectData implements LPSubjectData {
}
public Tristate getPermissionValue(ContextSet contexts, String permission) {
return permissionCache.getUnchecked(contexts).getCalculator().getPermissionValue(permission);
return permissionCache.get(contexts).getCalculator().getPermissionValue(permission);
}
public void replacePermissions(Map<ImmutableContextSet, Map<String, Boolean>> map) {
@@ -27,9 +27,8 @@ import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import me.lucko.luckperms.api.Tristate;
@@ -55,13 +54,8 @@ public class PersistedCollection implements LPSubjectCollection {
private final boolean transientHasPriority;
@Getter(AccessLevel.NONE)
private final LoadingCache<String, PersistedSubject> subjects = CacheBuilder.newBuilder()
.build(new CacheLoader<String, PersistedSubject>() {
@Override
public PersistedSubject load(String s) {
return new PersistedSubject(s, service, PersistedCollection.this);
}
});
private final LoadingCache<String, PersistedSubject> subjects = Caffeine.newBuilder()
.build(s -> new PersistedSubject(s, getService(), PersistedCollection.this));
public void loadAll() {
Map<String, SubjectStorageModel> holders = service.getStorage().loadAllFromFile(identifier);
@@ -73,7 +67,7 @@ public class PersistedCollection implements LPSubjectCollection {
@Override
public PersistedSubject get(@NonNull String id) {
return subjects.getUnchecked(id.toLowerCase());
return subjects.get(id.toLowerCase());
}
@Override
@@ -25,9 +25,8 @@ package me.lucko.luckperms.sponge.service.persisted;
import lombok.Getter;
import lombok.NonNull;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.api.Tristate;
@@ -67,31 +66,17 @@ public class PersistedSubject implements LPSubject {
private final PersistedSubjectData subjectData;
private final CalculatedSubjectData transientSubjectData;
private final LoadingCache<PermissionLookup, Tristate> permissionLookupCache = CacheBuilder.newBuilder()
private final LoadingCache<PermissionLookup, Tristate> permissionLookupCache = Caffeine.newBuilder()
.expireAfterAccess(20, TimeUnit.MINUTES)
.build(new CacheLoader<PermissionLookup, Tristate>() {
@Override
public Tristate load(PermissionLookup lookup) {
return lookupPermissionValue(lookup.getContexts(), lookup.getNode());
}
});
private final LoadingCache<ImmutableContextSet, Set<SubjectReference>> parentLookupCache = CacheBuilder.newBuilder()
.expireAfterAccess(20, TimeUnit.MINUTES)
.build(new CacheLoader<ImmutableContextSet, Set<SubjectReference>>() {
@Override
public Set<SubjectReference> load(ImmutableContextSet contexts) {
return lookupParents(contexts);
}
});
private final LoadingCache<OptionLookup, Optional<String>> optionLookupCache = CacheBuilder.newBuilder()
.expireAfterAccess(20, TimeUnit.MINUTES)
.build(new CacheLoader<OptionLookup, Optional<String>>() {
@Override
public Optional<String> load(OptionLookup lookup) {
return lookupOptionValue(lookup.getContexts(), lookup.getKey());
}
});
.build(lookup -> lookupPermissionValue(lookup.getContexts(), lookup.getNode()));
private final LoadingCache<ImmutableContextSet, Set<SubjectReference>> parentLookupCache = Caffeine.newBuilder()
.expireAfterAccess(20, TimeUnit.MINUTES)
.build(this::lookupParents);
private final LoadingCache<OptionLookup, Optional<String>> optionLookupCache = Caffeine.newBuilder()
.expireAfterAccess(20, TimeUnit.MINUTES)
.build(lookup -> lookupOptionValue(lookup.getContexts(), lookup.getKey()));
private final BufferedRequest<Void> saveBuffer = new BufferedRequest<Void>(1000L, r -> PersistedSubject.this.service.getPlugin().doAsync(r)) {
@Override
@@ -251,7 +236,7 @@ public class PersistedSubject implements LPSubject {
@Override
public Tristate getPermissionValue(@NonNull ContextSet contexts, @NonNull String node) {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.INTERNAL_SUBJECT_GET_PERMISSION_VALUE)) {
Tristate t = permissionLookupCache.getUnchecked(PermissionLookup.of(node, contexts.makeImmutable()));
Tristate t = permissionLookupCache.get(PermissionLookup.of(node, contexts.makeImmutable()));
service.getPlugin().getVerboseHandler().offer("local:" + getParentCollection().getCollection() + "/" + identifier, node, t);
return t;
}
@@ -275,14 +260,14 @@ public class PersistedSubject implements LPSubject {
@Override
public Set<SubjectReference> getParents(@NonNull ContextSet contexts) {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.INTERNAL_SUBJECT_GET_PARENTS)) {
return parentLookupCache.getUnchecked(contexts.makeImmutable());
return parentLookupCache.get(contexts.makeImmutable());
}
}
@Override
public Optional<String> getOption(ContextSet contexts, String key) {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.INTERNAL_SUBJECT_GET_OPTION)) {
return optionLookupCache.getUnchecked(OptionLookup.of(key, contexts.makeImmutable()));
return optionLookupCache.get(OptionLookup.of(key, contexts.makeImmutable()));
}
}
@@ -25,9 +25,8 @@ package me.lucko.luckperms.sponge.service.proxy;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.api.context.ContextSet;
@@ -41,28 +40,18 @@ import java.util.Set;
@UtilityClass
public class Util {
private static final LoadingCache<Set<Context>, ImmutableContextSet> SPONGE_TO_LP_CACHE = CacheBuilder.newBuilder()
.build(new CacheLoader<Set<Context>, ImmutableContextSet>() {
@Override
public ImmutableContextSet load(Set<Context> contexts) {
return ImmutableContextSet.fromEntries(contexts);
}
});
private static final LoadingCache<Set<Context>, ImmutableContextSet> SPONGE_TO_LP_CACHE = Caffeine.newBuilder()
.build(ImmutableContextSet::fromEntries);
private static final LoadingCache<ImmutableContextSet, Set<Context>> LP_TO_SPONGE_CACHE = CacheBuilder.newBuilder()
.build(new CacheLoader<ImmutableContextSet, Set<Context>>() {
@Override
public Set<Context> load(ImmutableContextSet set) {
return set.toSet().stream().map(e -> new Context(e.getKey(), e.getValue())).collect(ImmutableCollectors.toImmutableSet());
}
});
private static final LoadingCache<ImmutableContextSet, Set<Context>> LP_TO_SPONGE_CACHE = Caffeine.newBuilder()
.build(set -> set.toSet().stream().map(e -> new Context(e.getKey(), e.getValue())).collect(ImmutableCollectors.toImmutableSet()));
public static ContextSet convertContexts(@NonNull Set<Context> contexts) {
return SPONGE_TO_LP_CACHE.getUnchecked(ImmutableSet.copyOf(contexts));
return SPONGE_TO_LP_CACHE.get(ImmutableSet.copyOf(contexts));
}
public static Set<Context> convertContexts(@NonNull ContextSet contexts) {
return LP_TO_SPONGE_CACHE.getUnchecked(contexts.makeImmutable());
return LP_TO_SPONGE_CACHE.get(contexts.makeImmutable());
}
public static Tristate convertTristate(me.lucko.luckperms.api.Tristate tristate) {