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

MVP for new timeline syncs #464

Merged
merged 6 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ public int hashCode() {
return Objects.hash(time, name, sync, duration, windowStart, windowEnd, jump, jumpLabel, forceJump, icon, replaces, enabled, callout, calloutPreTime, getEnabledJobs());
}

@Override
public @Nullable EventSyncController eventSyncController() {
// TODO
return null;
}

@Override
public String toString() {
return "CustomTimelineEntry{" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public boolean isLabel() {
return true;
}

@Override
public @Nullable EventSyncController eventSyncController() {
return null;
}

@Override
public double time() {
return time;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package gg.xp.xivsupport.timelines;

import gg.xp.reevent.events.Event;

public interface EventSyncController {
boolean shouldSync(Event event);

Class<? extends Event> eventType();

default boolean isEditable() {
return false;
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package gg.xp.xivsupport.timelines;

import gg.xp.reevent.events.Event;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;

public class FileEventSyncController implements EventSyncController {

private final Class<? extends Event> eventType;
private final Predicate<Event> predicate;
private final Map<String, String> original;

public FileEventSyncController(Class<? extends Event> eventType, Predicate<Event> predicate, Map<String, String> original) {
this.eventType = eventType;
this.predicate = predicate;
this.original = new HashMap<>(original);
}

@Override
public boolean shouldSync(Event event) {
return predicate.test(event);
}

@Override
public Class<? extends Event> eventType() {
return eventType;
}

@Override
public String toString() {
return eventType.getSimpleName() + original;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public record TextFileLabelEntry(
double time,
String name
) implements TimelineEntry, Serializable {

@Override
public String toString() {
return "TextFileLabelEntry{" +
Expand Down Expand Up @@ -62,4 +63,9 @@ public double calloutPreTime() {
public boolean isLabel() {
return true;
}

@Override
public @Nullable EventSyncController eventSyncController() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package gg.xp.xivsupport.timelines;

import gg.xp.reevent.events.Event;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.Serializable;
import java.util.function.Predicate;
import java.util.regex.Pattern;

public record TextFileTimelineEntry(
Expand All @@ -14,8 +16,9 @@ public record TextFileTimelineEntry(
@NotNull TimelineWindow timelineWindow,
@Nullable Double jump,
@Nullable String jumpLabel,
boolean forceJump
) implements TimelineEntry, Serializable {
boolean forceJump,
EventSyncController eventSyncController) implements TimelineEntry, Serializable {

@Override
public String toString() {
return "TextFileTimelineEntry{" +
Expand All @@ -27,6 +30,7 @@ public String toString() {
", jump=" + jump +
", jumpLabel='" + jumpLabel + '\'' +
", forceJump=" + forceJump +
", syncCtrl=" + eventSyncController +
'}';
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gg.xp.xivsupport.timelines;

import com.fasterxml.jackson.annotation.JsonIgnore;
import gg.xp.reevent.events.Event;
import gg.xp.xivdata.data.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -52,11 +53,34 @@ default boolean shouldSync(double currentTime, String line) {
return sync.matcher(line).find();
}

@Nullable EventSyncController eventSyncController();

default boolean hasEventSync() {
return eventSyncController() != null;
};

default @Nullable Class<? extends Event> eventSyncType() {
EventSyncController esc = eventSyncController();
return esc == null ? null : esc.eventType();
}

default boolean shouldSync(double currentTime, Event event) {
EventSyncController syncControl = eventSyncController();
if (syncControl == null) {
return false;
}
boolean timesMatch = (currentTime >= getMinTime() && currentTime <= getMaxTime());
if (!timesMatch) {
return false;
}
return syncControl.shouldSync(event);
}

/**
* @return true if this timeline entry would ever cause a sync
*/
default boolean canSync() {
return sync() != null;
return sync() != null || hasEventSync();
}

/**
Expand Down Expand Up @@ -320,7 +344,7 @@ default Stream<String> makeTriggerTimelineEntries() {
}
String uniqueName = makeUniqueName();
String hideAllLine = "hideall \"%s\"".formatted(uniqueName);
String actualTimelineLine = new TextFileTimelineEntry(time(), uniqueName, null, null, TimelineWindow.DEFAULT, null, null, false).toTextFormat();
String actualTimelineLine = new TextFileTimelineEntry(time(), uniqueName, null, null, TimelineWindow.DEFAULT, null, null, false, null).toTextFormat();
return Stream.of(hideAllLine, actualTimelineLine);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gg.xp.xivsupport.timelines;

import com.fasterxml.jackson.databind.ObjectMapper;
import gg.xp.reevent.events.BaseEvent;
import gg.xp.reevent.events.CurrentTimeSource;
import gg.xp.reevent.events.EventContext;
import gg.xp.reevent.events.EventMaster;
Expand Down Expand Up @@ -238,10 +239,10 @@ public void changeZone(EventContext context, ZoneChangeEvent zoneChangeEvent) {
}

@HandleEvents(order = 40_000)
public void actLine(EventContext context, ACTLogLineEvent event) {
public void actLine(EventContext context, BaseEvent event) {
TimelineProcessor currentTimeline = this.currentTimeline;
if (currentTimeline != null) {
currentTimeline.processActLine(event);
currentTimeline.processEvent(event);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,56 @@
package gg.xp.xivsupport.timelines;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import gg.xp.reevent.events.Event;
import gg.xp.xivsupport.timelines.cbevents.CbEventTypes;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class TimelineParser {
public final class TimelineParser {

private static final Logger log = LoggerFactory.getLogger(TimelineParser.class);
// TODO: there are also windows that do not have a start + end, only a single number
private static final Pattern timelinePatternLong = Pattern.compile("^(?<time>\\d+\\.?\\d*) (?:\"(?<title>[^\"]*)\"| sync /(?<sync>.*)/| window (?:(?<windowStart>\\d*\\.?\\d*),)?(?<windowEnd>\\d*\\.?\\d*)| (?<forcejump>force)?jump (?:\"(?<jumplabel>[^\"]*)\"|(?<jump>\\d*\\.?\\d*))| duration (?<duration>\\d*\\.?\\d*))*(?:$|\\s*#.*$)");
private static final Pattern timelinePatternLong = Pattern.compile("^(?<time>\\d+\\.?\\d*)" +
// These can be in any order
// This non-capturing group matches one element each pass
" (?:\"(?<title>[^\"]*)\"|" +
" sync /(?<sync>.*)/|" +
" window (?:(?<windowStart>\\d*\\.?\\d*),)?(?<windowEnd>\\d*\\.?\\d*)|" +
" (?<forcejump>force)?jump (?:\"(?<jumplabel>[^\"]*)\"|(?<jump>\\d*\\.?\\d*))|" +
" (?<eventType>[A-Z].*) (?<eventCond>\\{[^}]*})|" +
" duration (?<duration>\\d*\\.?\\d*)" +
")*(?:$|\\s*#.*$)"
);

private static final Pattern timelineLabelPattern = Pattern.compile("^(?<time>\\d+\\.?\\d*) label \"(?<label>[^\"]*)\"");
private static final ObjectMapper mapper = JsonMapper.builder()
// get as close as possible to json5
.configure(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES, true)
.configure(JsonReadFeature.ALLOW_TRAILING_COMMA, true)
.configure(JsonReadFeature.ALLOW_SINGLE_QUOTES, true)
.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true)
.configure(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS, true)
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
.configure(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS, true)
.build();

private TimelineParser() {
}

public static List<TimelineEntry> parseMultiple(Collection<String> line) {
return line.stream().map(TimelineParser::parseRaw).filter(Objects::nonNull).collect(Collectors.toList());
Expand Down Expand Up @@ -54,12 +87,29 @@ public static List<TimelineEntry> parseMultiple(Collection<String> line) {
String jumpLabel = matcher.group("jumplabel");
String forceJumpRaw = matcher.group("forcejump");
String durationRaw = matcher.group("duration");
String eventTypeRaw = matcher.group("eventType");
EventSyncController esc = null;

if ("--sync--".equals(title)) {
title = null;
}
if (syncRaw == null) {
sync = null;
if (eventTypeRaw != null) {
CbEventTypes eventDef = CbEventTypes.valueOf(eventTypeRaw);
String eventCondRaw = matcher.group("eventCond");
// TODO: support translation for this
Map<String, String> conditions;
try {
conditions = mapper.readValue(eventCondRaw, new TypeReference<>() {
});
}
catch (JsonProcessingException e) {
throw new RuntimeException("Error reading JSON: " + eventCondRaw, e);
}
Predicate<Event> condition = eventDef.make(conditions);
esc = new FileEventSyncController(eventDef.eventType(), condition, conditions);
}
}
else {
sync = Pattern.compile(syncRaw, Pattern.CASE_INSENSITIVE);
Expand All @@ -86,7 +136,7 @@ public static List<TimelineEntry> parseMultiple(Collection<String> line) {
throw new IllegalArgumentException("Not a valid time: %s (entire line: %s)".formatted(timeRaw, line));
}
boolean forceJump = forceJumpRaw != null;
return new TextFileTimelineEntry(time, title, sync, doubleOrNull(durationRaw), window, doubleOrNull(jumpRaw), jumpLabel, forceJump);
return new TextFileTimelineEntry(time, title, sync, doubleOrNull(durationRaw), window, doubleOrNull(jumpRaw), jumpLabel, forceJump, esc);
}
log.trace("Line did not match: {}", line);
return null;
Expand Down
Loading
Loading