Skip to content

Commit

Permalink
Merge pull request #1 from kuba6000/tracking
Browse files Browse the repository at this point in the history
Job tracking (time spent on crafting etc...)
  • Loading branch information
kuba6000 authored Aug 22, 2024
2 parents 6c6a138 + 4ee29d9 commit f183aae
Show file tree
Hide file tree
Showing 18 changed files with 804 additions and 27 deletions.
11 changes: 11 additions & 0 deletions example_website/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,17 @@ button:hover{
#terminalcontent td{
height: auto;
}
#terminalcontent td.button{
background-color: #9e9e9e;
border: 0;
border-radius: 2px;
padding: 5px;
transition: 0.2s;
}
#terminalcontent td.button:hover{
background-color: #858585;
cursor: pointer;
}
#terminalsubcontainer{
width: 10%;
float: left;
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ apiPackage =
accessTransformersFile =

# Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled!
usesMixins = false
usesMixins = true

# Adds some debug arguments like verbose output and class export.
usesMixinDebug = false
Expand Down
103 changes: 103 additions & 0 deletions src/main/java/com/kuba6000/ae2webintegration/AE2Controller.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import com.kuba6000.ae2webintegration.utils.GSONUtils;
import com.kuba6000.ae2webintegration.utils.HTTPUtils;
import com.kuba6000.ae2webintegration.utils.VersionChecker;
import com.mojang.authlib.GameProfile;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
Expand All @@ -36,6 +37,7 @@
import appeng.api.storage.data.IItemList;
import appeng.me.Grid;
import appeng.me.cache.SecurityCache;
import cpw.mods.fml.common.registry.GameRegistry;

public class AE2Controller {

Expand Down Expand Up @@ -126,6 +128,19 @@ public SUBMIT_ORDER(int id) {

}

public static class TRACKING_LIST extends REQUEST_OPERATION {

}

public static class GET_TRACKING extends REQUEST_OPERATION {

int id;

public GET_TRACKING(int id) {
this.id = id;
}
}

public static ConcurrentLinkedQueue<REQUEST_OPERATION> requests = new ConcurrentLinkedQueue<>();

public static class AE2Data {
Expand Down Expand Up @@ -164,6 +179,7 @@ public static class ClusterData {
public IItemList<IAEItemStack> active = null;
public IItemList<IAEItemStack> pending = null;
public IItemList<IAEItemStack> storage = null;
public AE2JobTracker.JobTrackingInfo trackingInfo = null;

public void initItemLists() {
active = AEApi.instance()
Expand All @@ -182,6 +198,8 @@ public static class ClusterCompactedData {

public GSONItem finalOutput;
public ArrayList<CompactedItem> items;
public boolean hasTrackingInfo = false;
public long timeStarted = 0L;
}

public AE2Data invalid() {
Expand All @@ -205,6 +223,8 @@ public static void startHTTPServer() {
server.createContext("/items", new ItemsHandler());
server.createContext("/order", new OrderHandler());
server.createContext("/job", new JobHandler());
server.createContext("/trackinghistory", new TrackingHistoryHandler());
server.createContext("/gettracking", new GetTrackingHandler());
server.createContext("/", new WebHandler());
server.setExecutor(serverThread);
server.start();
Expand Down Expand Up @@ -259,6 +279,10 @@ public static class CompactedItem {
public long active = 0;
public long pending = 0;
public long stored = 0;
public long timeSpentCrafting = 0;
public long craftedTotal = 0;
public double shareInCraftingTime = 0d;
public double craftsPerSec = 0d;

@GSONUtils.SkipGSON
private int hashcode = 0;
Expand All @@ -268,6 +292,15 @@ public CompactedItem(String itemid, String itemname) {
this.itemname = itemname;
}

public static CompactedItem create(IAEItemStack stack) {
return new AE2Controller.CompactedItem(
GameRegistry.findUniqueIdentifierFor(stack.getItem())
.toString() + ":"
+ stack.getItemDamage(),
stack.getItemStack()
.getDisplayName());
}

@Override
public int hashCode() {
if (hashcode == 0) {
Expand Down Expand Up @@ -512,6 +545,75 @@ public void handle(HttpExchange t) throws IOException {

}

static class TrackingHistoryHandler implements HttpHandler {

static class TrackingHistoryElement {

public long timeStarted;
public long timeDone;
public boolean wasCancelled;
public IAEItemStack finalOutput;
public int id;
}

@Override
public void handle(HttpExchange t) throws IOException {

if (preHTTPHandler(t)) return;

ArrayList<TrackingHistoryElement> jobs = new ArrayList<>(AE2JobTracker.trackingInfos.size());

for (Map.Entry<Integer, AE2JobTracker.JobTrackingInfo> integerJobTrackingInfoEntry : AE2JobTracker.trackingInfos
.entrySet()) {
TrackingHistoryElement element = new TrackingHistoryElement();
element.id = integerJobTrackingInfoEntry.getKey();
element.timeStarted = integerJobTrackingInfoEntry.getValue().timeStarted;
element.timeDone = integerJobTrackingInfoEntry.getValue().timeDone;
element.wasCancelled = integerJobTrackingInfoEntry.getValue().wasCancelled;
element.finalOutput = integerJobTrackingInfoEntry.getValue().finalOutput;
jobs.add(element);
}

jobs.sort((i1, i2) -> Long.compare(i2.timeDone, i1.timeDone));

String response = GSONUtils.GSON_BUILDER.create()
.toJson(jobs);
byte[] raw_response = response.getBytes();
t.sendResponseHeaders(200, raw_response.length);
OutputStream os = t.getResponseBody();
os.write(raw_response);
os.close();
}
}

static class GetTrackingHandler implements HttpHandler {

@Override
public void handle(HttpExchange t) throws IOException {

if (preHTTPHandler(t)) return;

Map<String, String> GET_PARAMS = HTTPUtils.parseQueryString(
t.getRequestURI()
.getQuery());
if (!GET_PARAMS.containsKey("id")) {
return;
}
int id = Integer.parseInt(GET_PARAMS.get("id"));

AE2JobTracker.JobTrackingInfo info = AE2JobTracker.trackingInfos.get(id);
if (info == null) return;

String response = GSONUtils.GSON_BUILDER.create()
.toJson(info);
byte[] raw_response = response.getBytes();
t.sendResponseHeaders(200, raw_response.length);
OutputStream os = t.getResponseBody();
os.write(raw_response);
os.close();
}
}

static class WebHandler implements HttpHandler {

@Override
Expand Down Expand Up @@ -552,6 +654,7 @@ public void handle(HttpExchange t) throws IOException {
.collect(Collectors.joining(System.lineSeparator()));
}
}
response = response.replace("_REPLACE_ME_VERSION_OUTDATED", VersionChecker.isOutdated() ? "true" : "false");
byte[] raw_response = response.getBytes();
t.sendResponseHeaders(200, raw_response.length);
OutputStream os = t.getResponseBody();
Expand Down
178 changes: 178 additions & 0 deletions src/main/java/com/kuba6000/ae2webintegration/AE2JobTracker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package com.kuba6000.ae2webintegration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.kuba6000.ae2webintegration.mixins.AE2.CraftingCPUClusterAccessor;
import com.kuba6000.ae2webintegration.mixins.AE2.CraftingLinkAccessor;
import com.kuba6000.ae2webintegration.utils.GSONUtils;

import appeng.api.networking.crafting.ICraftingCPU;
import appeng.api.networking.crafting.ICraftingLink;
import appeng.api.storage.data.IAEItemStack;
import appeng.api.storage.data.IItemList;
import appeng.crafting.CraftingLink;
import appeng.me.cluster.implementations.CraftingCPUCluster;
import cpw.mods.fml.common.registry.GameRegistry;

public class AE2JobTracker {

public static class JobTrackingInfo {

public IAEItemStack finalOutput;
public long timeStarted;
public long timeDone;
public HashMap<IAEItemStack, Long> timeSpentOn = new HashMap<>();
public HashMap<IAEItemStack, Long> startedWaitingFor = new HashMap<>();
public HashMap<IAEItemStack, Long> craftedTotal = new HashMap<>();
public HashMap<IAEItemStack, Long> waitingFor = new HashMap<>();
public boolean isDone = false;
public boolean wasCancelled = false;

public long getTimeSpentOn(IAEItemStack stack) {
Long time = timeSpentOn.get(stack);
if (time == null) return 0L;
Long additionalTime = startedWaitingFor.get(stack);
if (additionalTime != null) {
time += System.nanoTime() - additionalTime;
}
return time;
}

public double getShareInCraftingTime(IAEItemStack stack) {
long total = 0L;
long stackTime = 0L;
for (IAEItemStack iaeItemStack : timeSpentOn.keySet()) {
long timeSpent = getTimeSpentOn(iaeItemStack);
total += timeSpent;
if (stack.isSameType(iaeItemStack)) {
stackTime = timeSpent;
}
}
if (total == 0L) return 1d;
return (double) stackTime / (double) total;
}
}

public static class CompactedJobTrackingInfo {

public static class CompactedTrackingGSONItem {

public String itemid;
public String itemname;
public long timeSpentOn;
public long craftedTotal;
public double shareInCraftingTime = 0d;
public double craftsPerSec = 0d;
}

public AE2Controller.GSONItem finalOutput;
public long timeStarted;
public long timeDone;
public boolean wasCancelled;
public ArrayList<CompactedTrackingGSONItem> items = new ArrayList<>();

public CompactedJobTrackingInfo(JobTrackingInfo info) {
this.finalOutput = GSONUtils.convertToGSONItem(info.finalOutput);
this.timeStarted = info.timeStarted;
this.timeDone = info.timeDone;
this.wasCancelled = info.wasCancelled;
for (Map.Entry<IAEItemStack, Long> iaeItemStackLongEntry : info.timeSpentOn.entrySet()) {
IAEItemStack stack = iaeItemStackLongEntry.getKey();
long spent = iaeItemStackLongEntry.getValue();
CompactedTrackingGSONItem item = new CompactedTrackingGSONItem();
item.itemid = GameRegistry.findUniqueIdentifierFor(stack.getItem())
.toString() + ":"
+ stack.getItemDamage();
item.itemname = stack.getItemStack()
.getDisplayName();
item.timeSpentOn = spent;
item.craftedTotal = info.craftedTotal.get(stack);
item.shareInCraftingTime = info.getShareInCraftingTime(stack);
item.craftsPerSec = (double) item.craftedTotal / (item.timeSpentOn / 1e9d);
items.add(item);
}
items.sort((i1, i2) -> Double.compare(i2.shareInCraftingTime, i1.shareInCraftingTime));
}
}

public static HashMap<ICraftingCPU, JobTrackingInfo> trackingInfoMap = new HashMap<>();
public static ConcurrentHashMap<Integer, JobTrackingInfo> trackingInfos = new ConcurrentHashMap<>();

private static int nextFreeTrackingInfoID = 1;

public static void addJob(ICraftingLink link) {
if (link instanceof CraftingLink craftingLink) {
ICraftingCPU cpu = ((CraftingLinkAccessor) craftingLink).callGetCpu();
if (cpu instanceof CraftingCPUCluster cpuCluster) {
JobTrackingInfo info;
trackingInfoMap.put(cpu, info = new JobTrackingInfo());
info.timeStarted = System.currentTimeMillis() / 1000L;
info.finalOutput = cpu.getFinalOutput()
.copy();
for (IAEItemStack iaeItemStack : ((CraftingCPUClusterAccessor) (Object) cpuCluster).getWaitingFor()) {
info.startedWaitingFor.put(iaeItemStack, System.nanoTime());
info.timeSpentOn.put(iaeItemStack, 0L);
info.craftedTotal.put(iaeItemStack, 0L);
info.waitingFor.put(iaeItemStack, iaeItemStack.getStackSize());
}
}
}
}

public static void updateCraftingStatus(ICraftingCPU cpu, IAEItemStack diff) {
JobTrackingInfo info = trackingInfoMap.get(cpu);
if (info == null) return;
CraftingCPUCluster cpuCluster = (CraftingCPUCluster) cpu;
IItemList<IAEItemStack> waitingFor = ((CraftingCPUClusterAccessor) (Object) cpuCluster).getWaitingFor();
IAEItemStack found = waitingFor.findPrecise(diff);
if (found != null && found.getStackSize() > 0L) {
if (!info.startedWaitingFor.containsKey(found)) {
info.startedWaitingFor.put(found, System.nanoTime());
info.timeSpentOn.putIfAbsent(found, 0L);
info.waitingFor.put(found, found.getStackSize());
} else {
long i = info.waitingFor.get(found);
long newi = found.getStackSize();
if (i > newi) {
info.craftedTotal.merge(found, i - newi, Long::sum);
}
info.waitingFor.put(found, newi);
}
} else {
if (info.startedWaitingFor.containsKey(diff)) {
Long started = info.startedWaitingFor.remove(diff);
Long elapsed = System.nanoTime() - started;
info.timeSpentOn.merge(diff, elapsed, Long::sum);
info.craftedTotal.merge(diff, info.waitingFor.remove(diff), Long::sum);
}
}
}

public static void completeCrafting(ICraftingCPU cpu) {
JobTrackingInfo info = trackingInfoMap.remove(cpu);
if (info == null) return;
for (Map.Entry<IAEItemStack, Long> iaeItemStackLongEntry : info.waitingFor.entrySet()) {
info.craftedTotal.merge(iaeItemStackLongEntry.getKey(), iaeItemStackLongEntry.getValue(), Long::sum);
}
info.waitingFor.clear();
for (Map.Entry<IAEItemStack, Long> iaeItemStackLongEntry : info.startedWaitingFor.entrySet()) {
info.timeSpentOn
.merge(iaeItemStackLongEntry.getKey(), System.nanoTime() - iaeItemStackLongEntry.getValue(), Long::sum);
}
info.startedWaitingFor.clear();
info.isDone = true;
info.timeDone = System.currentTimeMillis() / 1000L;
trackingInfos.put(nextFreeTrackingInfoID++, info);
}

public static void cancelCrafting(ICraftingCPU cpu) {
JobTrackingInfo info = trackingInfoMap.get(cpu);
if (info == null) return;
completeCrafting(cpu);
info.wasCancelled = true;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.kuba6000.ae2webintegration;

import com.kuba6000.ae2webintegration.commands.ReloadCommandHandler;
import com.kuba6000.ae2webintegration.utils.VersionChecker;

import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
Expand All @@ -17,7 +18,10 @@ public void preInit(FMLPreInitializationEvent event) {
Config.init(event.getSuggestedConfigurationFile());
Config.synchronizeConfiguration();

AE2WebIntegration.LOG.info("I am GIGAMOD at version " + Tags.VERSION);
AE2WebIntegration.LOG.info("AE2WebIntegration loading at version " + Tags.VERSION);
if (VersionChecker.isOutdated()) AE2WebIntegration.LOG.warn(
"You are not on latest version ! Consider updating to {} at https://github.com/kuba6000/AE2-Web-Integration/releases/latest",
VersionChecker.getLatestTag());

FMLCommonHandler.instance()
.bus()
Expand Down
Loading

0 comments on commit f183aae

Please sign in to comment.