Skip to content

Commit

Permalink
Improves memory consumption and execution time of online documentation
Browse files Browse the repository at this point in the history
AlexisDrogoul committed Apr 19, 2024

Verified

This commit was signed with the committer’s verified signature.
caixw caixw
1 parent f2d4188 commit 65a7ac0
Showing 5 changed files with 209 additions and 101 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package gaml.compiler.gaml.documentation;

public class DocumentedObject {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*******************************************************************************************************
*
* GamlDocumentationGraph.java, in gaml.compiler, is part of the source code of the GAMA modeling and simulation
* platform (v.2024-06).
*
* (c) 2007-2024 UMI 209 UMMISCO IRD/SU & Partners (IRIT, MIAT, ESPACE-DEV, CTU)
*
* Visit https://github.com/gama-platform/gama for license information and contacts.
*
********************************************************************************************************/
package gaml.compiler.gaml.documentation;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import org.eclipse.emf.common.util.URI;
import org.jgrapht.GraphType;
import org.jgrapht.Graphs;
import org.jgrapht.graph.AbstractBaseGraph;
import org.jgrapht.graph.DefaultGraphType;
import org.jgrapht.graph.EdgeSetFactory;
import org.jgrapht.graph.FastLookupGraphSpecificsStrategy;
import org.jgrapht.graph.IntrusiveEdgesSpecifics;
import org.jgrapht.graph.UniformIntrusiveEdgesSpecifics;

import gama.dev.DEBUG;

/**
* The Class GamlResourceGraph.
*/
public class GamlDocumentationGraph {

static {
DEBUG.ON();
}

/**
* The Class Graph.
*/
class InternalGraph extends AbstractBaseGraph<URI, Edge> {

/**
* Instantiates a new graph.
*/
public InternalGraph() {
super(null, null,
new DefaultGraphType.Builder().undirected().allowMultipleEdges(true).allowSelfLoops(false)
.weighted(false).allowCycles(true).build(),
new FastLookupGraphSpecificsStrategy<URI, Edge>() {
@Override
public Function<GraphType, IntrusiveEdgesSpecifics<URI, Edge>>
getIntrusiveEdgesSpecificsFactory() {
return type -> new UniformIntrusiveEdgesSpecifics<>(new ConcurrentHashMap<>()) {
public URI getEdgeTarget(final Edge e) {
try {
return super.getEdgeTarget(e);
} catch (Exception e1) {
return null;
}
}
};

}

@Override
public EdgeSetFactory<URI, Edge> getEdgeSetFactory() {
return vertex -> Collections.newSetFromMap(new ConcurrentHashMap<Edge, Boolean>());
}
});
}

@Override
public boolean addEdge(final URI sourceVertex, final URI targetVertex, final Edge e) {
addVertex(sourceVertex);
addVertex(targetVertex);
removeEdge(sourceVertex, targetVertex);
return super.addEdge(sourceVertex, targetVertex, e);
}

}

/** The regular imports. */
InternalGraph internal = new InternalGraph();

/**
* Reset.
*/
void reset() {
internal = new InternalGraph();
}

/**
* The Class Edge.
*/
class Edge {}

/**
* Predecessors of.
*
* @param newURI
* the new URI
* @return the sets the
*/
public Set<URI> predecessorsOf(final URI uri) {
if (!internal.containsVertex(uri)) return Collections.EMPTY_SET;

return Graphs.neighborSetOf(internal, uri);

// Set<Edge> incoming = internal.incomingEdgesOf(uri);
//
// if (incoming.isEmpty()) return Collections.EMPTY_SET;
// Set<URI> result = new HashSet<>();
// for (Edge edge : incoming) { result.add(Graphs.getOppositeVertex(internal, edge, uri)); }
// return result;
}

/**
* Successors of.
*
* @param newURI
* the new URI
* @return the sets the
*/
public Set<URI> successorsOf(final URI uri) {
if (!internal.containsVertex(uri)) return Collections.EMPTY_SET;
return Graphs.neighborSetOf(internal, uri);
// Set<Edge> outgoing = internal.outgoingEdgesOf(uri);
// if (outgoing.isEmpty()) return Collections.EMPTY_SET;
// Set<URI> result = new HashSet<>();
// for (Edge edge : outgoing) { result.add(Graphs.getOppositeVertex(internal, edge, uri)); }
// return result;
}

/**
* Adds the edge.
*
* @param from
* the from
* @param to
* the to
* @param label
* the label
*/
public void addEdge(final URI from, final URI to) {
internal.addEdge(from, to, new Edge());
}

/**
* Removes the node.
*
* @param uri
* the uri
*/
public void removeNode(final URI uri) {
internal.removeVertex(uri);
}

}
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
********************************************************************************************************/
package gaml.compiler.gaml.documentation;

import java.util.HashSet;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.eclipse.core.runtime.IProgressMonitor;
@@ -35,6 +36,9 @@ public class GamlResourceDocumentationTask {
/** The current generation. */
int currentGeneration;

/** The objects. */
HashSet<URI> objects = new HashSet<>();

/**
* Instantiates a new gaml resource documentation task.
*
@@ -65,9 +69,9 @@ protected IStatus run(final IProgressMonitor monitor) {
/**
* Increment generation.
*/
public void incrementGeneration() {
currentGeneration++;
public int incrementGeneration() {
queue = new ConcurrentLinkedQueue<>();
return currentGeneration++;
}

/**
Original file line number Diff line number Diff line change
@@ -44,35 +44,18 @@ public class GamlResourceDocumenter implements IDocManager {
/** The documentation queue. */
final Map<URI, GamlResourceDocumentationTask> documentationTasks = new ConcurrentHashMap();

/** The documentations from EObject to DocumentationNode. Key is the complete URI of the object */
final Map<URI, DocumentationNode> docIndexedByObjects = new ConcurrentHashMap();
/** The documented objects. */
final Map<URI, DocumentedObject> documentedObjects = new ConcurrentHashMap();

/**
* The references from Eobject to resources. Key is the complete URI of the object, value is the set of hashcodes of
* the URIs of open resources using this object
*/

Map<URI, Set<URI>> resourcesIndexedByObjects = new ConcurrentHashMap();

/**
* The references from resources to EObjets. Key is the URI of the resource, value is the set of hashcodes of the
* complete URIs of objects present in this resource (they might belong to other resources)
*/

Map<URI, Set<URI>> objectsIndexedByResources = new ConcurrentHashMap();

/**
* Adds the arbitrary documentation task.
* The Record DocumentedObject.
*
* @author Alexis Drogoul ([email protected])
* @param task
* the task
* @date 30 déc. 2023
* @param node
* the node
* @param resources
* the resources
*/
public void addDocumentationTask(final URI res, final Runnable run) {
if (run == null || !isTaskValid(res)) return;
getTaskFor(res).add(run);
}
record DocumentedObject(DocumentationNode node, Set<URI> resources) {}

/**
* Sets the gaml documentation.
@@ -90,8 +73,8 @@ public void addDocumentationTask(final URI res, final Runnable run) {
*/
@Override
public void setGamlDocumentation(final URI res, final EObject object, final IGamlDescription desc) {
addDocumentationTask(res,
() -> internalSetGamlDocumentation(res, getCurrentDocGenerationFor(res), object, desc));
if (!isTaskValid(res)) return;
getTaskFor(res).add(() -> internalSetGamlDocumentation(res, getCurrentDocGenerationFor(res), object, desc));
}

/**
@@ -111,9 +94,14 @@ boolean internalSetGamlDocumentation(final URI res, final int generation, final
try {
if (!isTaskValid(res) || !isValidGeneration(res, generation)) return false;
URI fragment = EcoreUtil.getURI(object);
docIndexedByObjects.put(fragment, new DocumentationNode(desc));
resourcesIndexedByObjects.computeIfAbsent(fragment, uri -> new HashSet()).add(res);
objectsIndexedByResources.computeIfAbsent(res, uri -> new HashSet()).add(fragment);
DocumentedObject documented = documentedObjects.get(fragment);
if (documented == null) {
documented = new DocumentedObject(new DocumentationNode(desc), new HashSet());
documentedObjects.put(fragment, documented);
}
documented.resources.add(res);
GamlResourceDocumentationTask task = getTaskFor(res);
task.objects.add(fragment);
return true;
} catch (final RuntimeException e) {
DEBUG.ERR("Error in documenting " + res.lastSegment(), e);
@@ -123,16 +111,15 @@ boolean internalSetGamlDocumentation(final URI res, final int generation, final

// To be called once the validation has been done
@Override
public void doDocument(final URI res, final ModelDescription desc,
public void doDocument(final URI res, final ModelDescription model,
final Map<EObject, IGamlDescription> additionalExpressions) {

GamlResourceDocumentationTask task;
task = getTaskFor(res);
task.incrementGeneration();
int generation = getCurrentDocGenerationFor(res);
GamlResourceDocumentationTask task = getTaskFor(res);
int generation = task.incrementGeneration();
task.add(() -> {
internalDoDocument(res, generation, desc);
recursiveDoDocument(res, generation, model);
additionalExpressions.forEach((e, d) -> internalSetGamlDocumentation(res, generation, e, d));
// Important to do it here, once all the documentation has been produced
model.dispose();
});
}

@@ -146,91 +133,47 @@ public void doDocument(final URI res, final ModelDescription desc,
* the desc
* @date 31 déc. 2023
*/
private boolean internalDoDocument(final URI resource, final int generation, final IDescription desc) {
private boolean recursiveDoDocument(final URI resource, final int generation, final IDescription desc) {
if (desc == null) return false;
final EObject e = desc.getUnderlyingElement();
if (e == null) return true; // We return true to continue exploring if other descriptions should be documented
if (!internalSetGamlDocumentation(resource, generation, e, desc)) return false;
return desc.visitOwnChildren(d -> internalDoDocument(resource, generation, d));
return desc.visitOwnChildren(d -> recursiveDoDocument(resource, generation, d));
}

@Override
public IGamlDescription getGamlDocumentation(final EObject object) {
if (object == null) return null;
IGamlDescription doc = docIndexedByObjects.get(EcoreUtil.getURI(object));
// if (doc == null && DEBUG.IS_ON()) {
// DEBUG.OUT("EObject " + object + " in resource "
// + (object.eResource() == null ? "null" : object.eResource().getURI().lastSegment())
// + " is not documented ");
// }
return doc;
DocumentedObject doc = documentedObjects.get(EcoreUtil.getURI(object));
return doc == null ? null : doc.node;
}

@Override
public void invalidate(final URI uri) {
if (uri == null) return;
// Should we do it immediately ? with the following reasoning:
// 1/ If called from closing the editor, no reason to do it at the end and not immediately
// 2/ If called from the erasing of cache in GamlResource, it must be done immediately to allow the new Eobjects
// to not be mixed with the "old" ones
// addDocumentationTask(d -> {
Set<URI> objects = objectsIndexedByResources.remove(uri);
documentationTasks.remove(uri);
GamlResourceDocumentationTask task = documentationTasks.remove(uri);
Set<URI> objects = task == null ? null : task.objects;
if (objects != null) {
objects.forEach(object -> {
Set<URI> resources = resourcesIndexedByObjects.get(object);
DocumentedObject documented = documentedObjects.get(object);
Set<URI> resources = documented == null ? null : documented.resources;
if (resources != null) {
resources.remove(uri);
if (resources.isEmpty()) {
docIndexedByObjects.remove(object);
resourcesIndexedByObjects.remove(object);
}
if (resources.isEmpty()) { documentedObjects.remove(object); }
}
});
}
// if (DEBUG.IS_ON()) { debugStatistics("Invalidation of " + uri.lastSegment()); }
// });

/**
* Debug statistics.
*
* @param title
* the title
*/
}

/**
* Debug statistics.
*
* @author Alexis Drogoul ([email protected])
* @param title
* the title
* @date 31 déc. 2023
*/
// private void debugStatistics(final String title) {
// DEBUG.SECTION(title);
// DEBUG.BANNER("DOC", "docIndexedByObjects", "size", String.valueOf(docIndexedByObjects.size()));
// DEBUG.BANNER("DOC", "eObjectsIndexedByResources", "size", String.valueOf(objectsIndexedByResources.size()));
// DEBUG.BANNER("DOC", "Opened Resources", "names", new HashSet(resourceNames.values()).toString());
// DEBUG.BANNER("DOC", "resourcesIndexedByEObjects", "size", String.valueOf(resourcesIndexedByObjects.size()));
// DEBUG.LINE();
// /**
// * Invalidate all.
// */
// }

/**
* Invalidate all.
*
* @author Alexis Drogoul ([email protected])
* @date 30 déc. 2023
*/
public void invalidateAll() {
// if (DEBUG.IS_ON()) { debugStatistics("Before clean build"); }
getTaskFor(URI.createURI("")).add(() -> {
docIndexedByObjects.clear();
resourcesIndexedByObjects.clear();
objectsIndexedByResources.clear();
documentedObjects.clear();
documentationTasks.clear();
});

}
10 changes: 3 additions & 7 deletions gaml.compiler/src/gaml/compiler/gaml/resource/GamlResource.java
Original file line number Diff line number Diff line change
@@ -219,13 +219,9 @@ public void validate() {
try {
updateWith(model.validate(), true);
} finally {
// make sure to get rid of the model only after its documentation has been produced
if (GamlResourceServices.isEdited(this.getURI())) {
GamlResourceServices.getResourceDocumenter().addDocumentationTask(getURI(), () -> model.dispose());
} else {
model.dispose();
}
// }
// make sure to get rid of the model only if its documentation is not being produced (in which case the
// model is disposed by the documenter
if (!GamlResourceServices.isEdited(this.getURI())) { model.dispose(); }
}
}

0 comments on commit 65a7ac0

Please sign in to comment.