/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.scenegraph.profile.common; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import org.simantics.Simantics; import org.simantics.db.AsyncRequestProcessor; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.common.session.SessionEventListenerAdapter; import org.simantics.db.common.utils.Logger; import org.simantics.db.procedure.Procedure; import org.simantics.db.service.SessionEventSupport; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.G2DSceneGraph; import org.simantics.scenegraph.profile.EvaluationContext; import org.simantics.scenegraph.profile.ProfileEntry; import org.simantics.scenegraph.profile.Style; import org.simantics.scenegraph.profile.impl.DebugPolicy; import org.simantics.scenegraph.profile.impl.ProfileActivationListener; import org.simantics.scenegraph.profile.request.RuntimeProfileActiveEntries; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.datastructures.disposable.IDisposable; import org.simantics.utils.threads.IThreadWorkQueue; import org.simantics.utils.threads.ThreadUtils; public class ProfileObserver implements EvaluationContext { private final Session session; /** * Runtime diagram resource. */ private final Resource resource; private final IDisposable canvas; private final IThreadWorkQueue thread; @SuppressWarnings("unused") private final Object diagram; private final Runnable notification; private final G2DSceneGraph sceneGraph; private volatile boolean dirty = true; private volatile boolean disposed = false; private List> updates = new ArrayList<>(); private boolean updateAll; private ProfileActivationListener activationListener; private Map constants = new HashMap(); private Map> temporaryProperties = new HashMap>(); private Map> properties = new HashMap>(); private final SessionEventListenerAdapter transactionListener = new SessionEventListenerAdapter() { @Override public void writeTransactionFinished() { if (isDisposed()) dispose(); if (dirty) perform(); } @Override public void readTransactionFinished() { if (isDisposed()) dispose(); if (dirty) perform(); } }; public ProfileObserver(Session session, Resource resource, IThreadWorkQueue thread, IDisposable canvas, G2DSceneGraph sceneGraph, Object diagram, Map constants, Runnable notification) { //System.out.println(this + " NEW PROFILE OBSERVER: "); this.session = session; this.resource = resource; this.thread = thread; this.canvas = canvas; this.diagram = diagram; this.sceneGraph = sceneGraph; this.constants.putAll(constants); this.notification = notification; attachSessionListener(); if (DebugPolicy.DEBUG_PROFILE_OBSERVER_PERFORM) System.out.println("ProfileObserver(" + this + ")"); // Tell SceneGraph that this observer is not yet done applying its operations if(sceneGraph != null) sceneGraph.setPending(ProfileObserver.this); } private void attachSessionListener() { SessionEventSupport eventSupport = session.getService(SessionEventSupport.class); eventSupport.addListener(transactionListener); } private void detachSessionListener() { SessionEventSupport eventSupport = session.getService(SessionEventSupport.class); eventSupport.removeListener(transactionListener); } public void dispose() { synchronized (this) { if (disposed) return; disposed = true; } if (DebugPolicy.DEBUG_PROFILE_OBSERVER_PERFORM) System.out.println("ProfileObserver.dispose(" + this + ")"); if(activationListener != null) { activationListener.cleanup(); activationListener = null; } detachSessionListener(); } @Override public void update(Style style, Object item) { if (DebugPolicy.DEBUG_PROFILE_OBSERVER_UPDATE) System.out.println("Profile observer marked dirty."); updates.add(Pair.make(style, item)); //updateAll = true; dirty = true; } public void update() { updateAll = true; dirty = true; } private void perform() { dirty = false; if (DebugPolicy.DEBUG_PROFILE_OBSERVER_UPDATE) System.out.println("Profile observer detected a change."); session.asyncRequest(new RuntimeProfileActiveEntries(resource), new Procedure>() { @Override public void execute(final Collection entries) { if (isDisposed()) return; ThreadUtils.asyncExec(thread, new Runnable() { @Override public void run() { if (isDisposed()) return; temporaryProperties.clear(); long t0 = DebugPolicy.DEBUG_PROFILE_OBSERVER_PERFORM ? System.nanoTime() : 0L; if (updateAll) { for(ProfileEntry e : entries) { if (DebugPolicy.DEBUG_PROFILE_OBSERVER_PERFORM) System.out.println("Apply profile entry: " + e); e.apply(ProfileObserver.this); } updateAll = false; updates.clear(); } else { List> updatesCopy = new ArrayList<>(updates); updates.clear(); for (Pair update : updatesCopy) { Style style = update.first; Object item = update.second; style.apply2(item, ProfileObserver.this); } } if (DebugPolicy.DEBUG_PROFILE_OBSERVER_PERFORM) { long t1 = System.nanoTime(); System.out.println((t1-t0) / 1e6); } if(dirty) { sceneGraph.setPending(ProfileObserver.this); // System.err.println("setPending, dirty=true"); } else { sceneGraph.clearPending(ProfileObserver.this); // System.err.println("clearPending, dirty=false"); } notification.run(); // canvas.getContentContext().setDirty(); // Something is underway, schedule update if (dirty) { Simantics.async(() -> { if (isDisposed()) return; if (dirty) perform(); }, 100, TimeUnit.MILLISECONDS); } } }); } @Override public void exception(Throwable t) { Logger.defaultLogError(t); } }); } @Override public boolean isDisposed() { return disposed || canvas.isDisposed(); } @Override public void exception(Throwable throwable) { Logger.defaultLogError(throwable); } @SuppressWarnings("unchecked") @Override public T getTemporaryProperty(INode element, String key) { Map map = temporaryProperties.get(element); T t = map == null ? null : (T) map.get(key); //System.out.println(this + ".getTemporaryProperty(" + element + ", " + key + "): " + t); return t; } @Override public void setTemporaryProperty(INode element, String key, T value) { //System.out.println(this + ".setTemporaryProperty(" + element + ", " + key + ", " + value + ")"); Map map = temporaryProperties.get(element); if (map == null) { if (value == null) return; map = new HashMap(8); temporaryProperties.put(element, map); } if (value == null) { map.remove(key); if (map.isEmpty()) temporaryProperties.remove(element); } else map.put(key, value); } @SuppressWarnings("unchecked") @Override public T getProperty(INode element, String key) { Map map = properties.get(element); T t = map == null ? null : (T) map.get(key); //System.out.println(this + ".getProperty(" + element + ", " + key + "): " + t); return t; } @SuppressWarnings("unchecked") @Override public T setProperty(INode element, String key, T value) { T result = null; //System.out.println(this + ".setProperty(" + element + ", " + key + ", " + value + ")"); Map map = properties.get(element); if (map == null) { if (value == null) return null; map = new HashMap(8); properties.put(element, map); } if (value == null) { result = (T) map.remove(key); if (map.isEmpty()) properties.remove(element); } else result = (T) map.put(key, value); return result; } @SuppressWarnings("unchecked") @Override public T getConstant(String key) { return (T) constants.get(key); } @Override public String toString() { return "ProfileObserver[" + resource.getResourceId() + "]"; } // @Override // public ICanvasContext getContext() { // return canvas; // } // // @Override // public IDiagram getDiagram() { // return diagram; // } public void listen(AsyncRequestProcessor processor, IDisposable disposable) { activationListener = new ProfileActivationListener(resource, this, disposable); processor.asyncRequest(new RuntimeProfileActiveEntries(resource), activationListener); } @Override public Resource getResource() { return resource; } @Override public G2DSceneGraph getSceneGraph() { return sceneGraph; } }