net.minecraftforge.common.model.animation.AnimationStateMachine.java Source code

Java tutorial

Introduction

Here is the source code for net.minecraftforge.common.model.animation.AnimationStateMachine.java

Source

/*
 * Minecraft Forge
 * Copyright (c) 2016.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation version 2.1
 * of the License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package net.minecraftforge.common.model.animation;

import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Supplier;
import com.google.common.collect.*;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.animation.Event;
import net.minecraftforge.common.animation.ITimeValue;
import net.minecraftforge.common.animation.TimeValues;
import net.minecraftforge.common.model.IModelState;
import net.minecraftforge.common.util.JsonUtils;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.Level;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gson.annotations.SerializedName;

public final class AnimationStateMachine implements IAnimationStateMachine {
    private final ImmutableMap<String, ITimeValue> parameters;
    private final ImmutableMap<String, IClip> clips;
    private final ImmutableList<String> states;
    private final ImmutableMultimap<String, String> transitions;
    @SerializedName("start_state")
    private final String startState;

    private transient boolean shouldHandleSpecialEvents;
    private transient String currentStateName;
    private transient IClip currentState;
    private transient float lastPollTime;

    private static final LoadingCache<Triple<? extends IClip, Float, Float>, Pair<IModelState, Iterable<Event>>> clipCache = CacheBuilder
            .newBuilder().maximumSize(100).expireAfterWrite(100, TimeUnit.MILLISECONDS)
            .build(new CacheLoader<Triple<? extends IClip, Float, Float>, Pair<IModelState, Iterable<Event>>>() {
                public Pair<IModelState, Iterable<Event>> load(Triple<? extends IClip, Float, Float> key)
                        throws Exception {
                    return Clips.apply(key.getLeft(), key.getMiddle(), key.getRight());
                }
            });

    @Deprecated
    public AnimationStateMachine(ImmutableMap<String, ITimeValue> parameters, ImmutableMap<String, IClip> clips,
            ImmutableList<String> states, ImmutableMap<String, String> transitions, String startState) {
        this(parameters, clips, states, ImmutableMultimap.copyOf(Multimaps
                .newSetMultimap(Maps.transformValues(transitions, new Function<String, Collection<String>>() {
                    public Collection<String> apply(String input) {
                        return ImmutableSet.of(input);
                    }
                }), new Supplier<Set<String>>() {
                    public Set<String> get() {
                        return Sets.newHashSet();
                    }
                })), startState);
    }

    public AnimationStateMachine(ImmutableMap<String, ITimeValue> parameters, ImmutableMap<String, IClip> clips,
            ImmutableList<String> states, ImmutableMultimap<String, String> transitions, String startState) {
        this.parameters = parameters;
        this.clips = clips;
        this.states = states;
        this.transitions = transitions;
        this.startState = startState;
    }

    /**
     * post-loading initialization hook.
     */
    void initialize() {
        if (parameters == null) {
            throw new JsonParseException("Animation State Machine should contain \"parameters\" key.");
        }
        if (clips == null) {
            throw new JsonParseException("Animation State Machine should contain \"clips\" key.");
        }
        if (states == null) {
            throw new JsonParseException("Animation State Machine should contain \"states\" key.");
        }
        if (transitions == null) {
            throw new JsonParseException("Animation State Machine should contain \"transitions\" key.");
        }
        shouldHandleSpecialEvents = true;
        lastPollTime = Float.NEGATIVE_INFINITY;
        // setting the starting state
        IClip state = clips.get(startState);
        if (!clips.containsKey(startState) || !states.contains(startState)) {
            throw new IllegalStateException("unknown state: " + startState);
        }
        currentStateName = startState;
        currentState = state;
    }

    public Pair<IModelState, Iterable<Event>> apply(float time) {
        if (lastPollTime == Float.NEGATIVE_INFINITY) {
            lastPollTime = time;
        }
        Pair<IModelState, Iterable<Event>> pair = clipCache
                .getUnchecked(Triple.of(currentState, lastPollTime, time));
        lastPollTime = time;
        boolean shouldFilter = false;
        if (shouldHandleSpecialEvents) {
            for (Event event : ImmutableList.copyOf(pair.getRight()).reverse()) {
                if (event.event().startsWith("!")) {
                    shouldFilter = true;
                    if (event.event().startsWith("!transition:")) {
                        String newState = event.event().substring("!transition:".length());
                        transition(newState);
                    } else {
                        System.out.println("Unknown special event \"" + event.event() + "\", ignoring");
                    }
                }
            }
        }
        if (!shouldFilter) {
            return pair;
        }
        return Pair.of(pair.getLeft(), Iterables.filter(pair.getRight(), new Predicate<Event>() {
            public boolean apply(Event event) {
                return !event.event().startsWith("!");
            }
        }));
    }

    public void transition(String newState) {
        IClip nc = clips.get(newState);
        if (!clips.containsKey(newState) || !states.contains(newState)) {
            throw new IllegalStateException("unknown state: " + newState);
        }
        if (!transitions.containsEntry(currentStateName, newState)) {
            throw new IllegalArgumentException("no transition from current clip \"" + currentStateName
                    + "\" to the clip \"" + newState + "\" found.");
        }
        currentStateName = newState;
        currentState = nc;
    }

    public String currentState() {
        return currentStateName;
    }

    public void shouldHandleSpecialEvents(boolean value) {
        shouldHandleSpecialEvents = true;
    }

    /**
     * Load a new instance if AnimationStateMachine at specified location, with specified custom parameters.
     */
    @SideOnly(Side.CLIENT)
    public static IAnimationStateMachine load(IResourceManager manager, ResourceLocation location,
            ImmutableMap<String, ITimeValue> customParameters) {
        try {
            ClipResolver clipResolver = new ClipResolver();
            ParameterResolver parameterResolver = new ParameterResolver(customParameters);
            Clips.CommonClipTypeAdapterFactory.INSTANCE.setClipResolver(clipResolver);
            TimeValues.CommonTimeValueTypeAdapterFactory.INSTANCE.setValueResolver(parameterResolver);
            IResource resource = manager.getResource(location);
            AnimationStateMachine asm = asmGson.fromJson(new InputStreamReader(resource.getInputStream(), "UTF-8"),
                    AnimationStateMachine.class);
            clipResolver.asm = asm;
            parameterResolver.asm = asm;
            asm.initialize();
            //String json = asmGson.toJson(asm);
            //System.out.println(location + ": " + json);
            return asm;
        } catch (IOException e) {
            FMLLog.log(Level.ERROR, e, "Exception loading Animation State Machine %s, skipping", location);
            return missing;
        } catch (JsonParseException e) {
            FMLLog.log(Level.ERROR, e, "Exception loading Animation State Machine %s, skipping", location);
            return missing;
        } finally {
            Clips.CommonClipTypeAdapterFactory.INSTANCE.setClipResolver(null);
            TimeValues.CommonTimeValueTypeAdapterFactory.INSTANCE.setValueResolver(null);
        }
    }

    private static final AnimationStateMachine missing = new AnimationStateMachine(
            ImmutableMap.<String, ITimeValue>of(),
            ImmutableMap.of("missingno", (IClip) Clips.IdentityClip.INSTANCE), ImmutableList.of("missingno"),
            ImmutableMultimap.<String, String>of(), "missingno");

    static {
        missing.initialize();
    }

    public static AnimationStateMachine getMissing() {
        return missing;
    }

    private static final class ClipResolver implements Function<String, IClip> {
        private AnimationStateMachine asm;

        public IClip apply(String name) {
            return asm.clips.get(name);
        }
    }

    private static final class ParameterResolver implements Function<String, ITimeValue> {
        private final ImmutableMap<String, ITimeValue> customParameters;
        private AnimationStateMachine asm;

        public ParameterResolver(ImmutableMap<String, ITimeValue> customParameters) {
            this.customParameters = customParameters;
        }

        public ITimeValue apply(String name) {
            if (asm.parameters.containsKey(name)) {
                return asm.parameters.get(name);
            }
            return customParameters.get(name);
        }
    }

    private static final Gson asmGson = new GsonBuilder()
            .registerTypeAdapter(ImmutableList.class, JsonUtils.ImmutableListTypeAdapter.INSTANCE)
            .registerTypeAdapter(ImmutableMap.class, JsonUtils.ImmutableMapTypeAdapter.INSTANCE)
            .registerTypeAdapterFactory(Clips.CommonClipTypeAdapterFactory.INSTANCE)
            //.registerTypeAdapterFactory(ClipProviders.CommonClipProviderTypeAdapterFactory.INSTANCE)
            .registerTypeAdapterFactory(TimeValues.CommonTimeValueTypeAdapterFactory.INSTANCE)
            .registerTypeAdapterFactory(TransitionsAdapterFactory.INSTANCE).setPrettyPrinting()
            .enableComplexMapKeySerialization().disableHtmlEscaping().create();

    private enum TransitionsAdapterFactory implements TypeAdapterFactory {
        INSTANCE;

        @SuppressWarnings("unchecked")
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            if (type.getRawType() != ImmutableMultimap.class || !(type.getType() instanceof ParameterizedType)) {
                return null;
            }
            final Type[] typeArguments = ((ParameterizedType) type.getType()).getActualTypeArguments();
            if (typeArguments.length != 2 || typeArguments[0] != String.class || typeArguments[1] != String.class) {
                return null;
            }
            final TypeAdapter<Map<String, Collection<String>>> mapAdapter = gson
                    .getAdapter(new TypeToken<Map<String, Collection<String>>>() {
                    });
            final TypeAdapter<Collection<String>> collectionAdapter = gson
                    .getAdapter(new TypeToken<Collection<String>>() {
                    });
            return (TypeAdapter<T>) new TypeAdapter<ImmutableMultimap<String, String>>() {
                @Override
                public void write(JsonWriter out, ImmutableMultimap<String, String> value) throws IOException {
                    mapAdapter.write(out, value.asMap());
                }

                @Override
                public ImmutableMultimap<String, String> read(JsonReader in) throws IOException {
                    ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder();
                    in.beginObject();
                    while (in.hasNext()) {
                        String key = in.nextName();
                        switch (in.peek()) {
                        case STRING:
                            builder.put(key, in.nextString());
                            break;
                        case BEGIN_ARRAY:
                            builder.putAll(key, collectionAdapter.read(in));
                            break;
                        default:
                            throw new JsonParseException("Expected String or Array, got " + in.peek());
                        }
                    }
                    in.endObject();
                    return builder.build();
                }
            };
        }
    }
}