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

Tilemap editor plugins #7665

Draft
wants to merge 9 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.defold.extension.editor;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questionable if the interface for editor plugins should be located here in bob?


import java.util.List;

import com.dynamo.gamesys.proto.Tile.TileCell;

import com.defold.extension.editor.TilemapPlugins.TilemapLayer;

public interface ITilemapPlugin {

public List<TileCell> onClearTile(int x, int y, TilemapLayer layer);
public List<TileCell> onPaintTile(int x, int y, TilemapLayer layer);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.defold.extension.editor;

import java.lang.reflect.Field;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import com.dynamo.bob.plugin.PluginScanner;
import com.dynamo.bob.fs.DefaultFileSystem;
import com.dynamo.bob.Project;

import com.dynamo.bob.ClassLoaderScanner;
import com.dynamo.gamesys.proto.Tile.TileCell;


public class TilemapPlugins {

/**
* Wrapper for the cells of a tilemap layer. Each cell is
* keyed on an index created from the x and y position of
* the cell.
*/
public static class TilemapLayer {

private Map<Long, Object> cells;
private String layerId;

public TilemapLayer(Map<Long, Object> cells, String layerId) {
this.cells = cells;
this.layerId = layerId;
}

public String getLayerId() {
return layerId;
}

public long xyToIndex(int x, int y) {
// 4294967295L = 0xFFFFFFFF
// using 0xFFFFFFFF messes up the & operation for some reason
return (y << Integer.SIZE) | (x & 4294967295L);
}

public Integer get(int x, int y) {
Object cell = cells.get(xyToIndex(x, y));
if (cell == null) {
return null;
}
try {
// It would be better if the (defrecord Tile) in tile_map.clj
// implemented a shared interface so that we don't have to use
// reflection to get the field(s) of the tile
Class<?> cls = cell.getClass();
Field field = cls.getField("tile");
Long value = (Long)field.get(cell);
return new Integer(value.intValue());
}
catch (Exception e) {
System.err.println("Unable to get field: " + e.getMessage());
}
return null;
}
}


private static List<ITilemapPlugin> tilemapPlugins = null;

public static void init(ClassLoader loader) {
if (tilemapPlugins != null) {
return;
}
ClassLoaderScanner scanner = new ClassLoaderScanner(loader);
try {
tilemapPlugins = PluginScanner.getOrCreatePlugins(scanner, "com.defold.extension.editor", ITilemapPlugin.class);
}
catch(Exception e) {
System.err.println("Exception while scanning for tilemap plugins");
tilemapPlugins = new ArrayList<>();
}
}

public static List<TileCell> onClearTile(int x, int y, Map<Long, Object> cells, String layerId) {
List<TileCell> changedCells = new ArrayList<>();
if (tilemapPlugins != null) {
TilemapLayer layer = new TilemapLayer(cells, layerId);
for (ITilemapPlugin plugin : tilemapPlugins) {
changedCells.addAll(plugin.onClearTile(x, y, layer));
}
}
return changedCells;
}
public static List<TileCell> onPaintTile(int x, int y, Map<Long, Object> cells, String layerId) {
List<TileCell> changedCells = new ArrayList<>();
if (tilemapPlugins != null) {
TilemapLayer layer = new TilemapLayer(cells, layerId);
for (ITilemapPlugin plugin : tilemapPlugins) {
changedCells.addAll(plugin.onPaintTile(x, y, layer));
}
}
return changedCells;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.util.HashMap;
import java.lang.reflect.Modifier;

import com.dynamo.bob.Bob;
import com.dynamo.bob.Project;
import com.dynamo.bob.IClassScanner;
import com.dynamo.bob.CompileExceptionError;
Expand All @@ -33,6 +32,16 @@ public class PluginScanner {

private static HashMap<String, Object> pluginsCache = new HashMap<>();


public static <T> T getOrCreatePlugin(String packageName, Class<T> pluginBaseClass) throws CompileExceptionError {
IClassScanner scanner = Project.getClassLoaderScanner();
if (scanner == null) {
logger.info("PluginScanner has no class loader scanner");
return null;
}
return getOrCreatePlugin(scanner, packageName, pluginBaseClass);
}

/**
* Get a previously cached instance or find and create an instance of a class
* extending a specific base class and located within a specific package. The
Expand All @@ -45,13 +54,14 @@ public class PluginScanner {
* This function will generate a CompileExceptionError if more than one
* class in the package path extends the same base class.
*
* @param scanner
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to be able to pass classloader scanner from the editor

* @param packageName
* @param pluginBaseClass
* @return Class instance or null if no class was found
*/
public static <T> T getOrCreatePlugin(String packageName, Class<T> pluginBaseClass) throws CompileExceptionError {
public static <T> T getOrCreatePlugin(IClassScanner scanner, String packageName, Class<T> pluginBaseClass) throws CompileExceptionError {

List<T> plugins = getOrCreatePlugins(packageName, pluginBaseClass);
List<T> plugins = getOrCreatePlugins(scanner, packageName, pluginBaseClass);
T plugin = null;
if (plugins != null) {
if (plugins.size() > 1) {
Expand All @@ -62,6 +72,16 @@ public static <T> T getOrCreatePlugin(String packageName, Class<T> pluginBaseCla
}
return plugin;
}


public static <T> List<T> getOrCreatePlugins(String packageName, Class<T> pluginBaseClass) throws CompileExceptionError {
IClassScanner scanner = Project.getClassLoaderScanner();
if (scanner == null) {
logger.info("PluginScanner has no class loader scanner");
return null;
}
return getOrCreatePlugins(scanner, packageName, pluginBaseClass);
}

/**
* Get a previously cached instances or find and create instances of classes
Expand All @@ -72,11 +92,12 @@ public static <T> T getOrCreatePlugin(String packageName, Class<T> pluginBaseCla
* - Not be abstract
* - Extend the base class
*
* @param scanner
* @param packageName
* @param pluginBaseClass
* @return List with class instances or null if no class was found
*/
public static <T> List<T> getOrCreatePlugins(String packageName, Class<T> pluginBaseClass) throws CompileExceptionError {
public static <T> List<T> getOrCreatePlugins(IClassScanner scanner, String packageName, Class<T> pluginBaseClass) throws CompileExceptionError {

// check if we've already searched for and cached a plugin for this package path and base class
// and if that is the case return the cached instance
Expand All @@ -92,12 +113,6 @@ public static <T> List<T> getOrCreatePlugins(String packageName, Class<T> plugin
return plugins;
}

IClassScanner scanner = Project.getClassLoaderScanner();
if (scanner == null) {
logger.info("PluginScanner has no class loader scanner");
return null;
}

logger.info("PluginScanner searching %s for base class %s", packageName, pluginBaseClass);

List<T> plugins = new ArrayList<>();
Expand Down
54 changes: 40 additions & 14 deletions editor/src/clj/editor/tile_map.clj
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
[editor.tile-source :as tile-source]
[editor.validation :as validation]
[editor.workspace :as workspace])
(:import [com.dynamo.gamesys.proto Tile$TileGrid Tile$TileGrid$BlendMode Tile$TileLayer]
(:import [com.dynamo.gamesys.proto Tile$TileGrid Tile$TileGrid$BlendMode Tile$TileLayer Tile$TileCell]
[com.defold.extension.editor TilemapPlugins]
[com.jogamp.opengl GL2]
[editor.gl.shader ShaderLifecycle]
[javax.vecmath Matrix4d Point3d Vector3d]))
Expand Down Expand Up @@ -84,21 +85,44 @@
(bit-or (bit-shift-left y Integer/SIZE)
(bit-and x 0xFFFFFFFF)))

(defn update-cell-from-pb
[cell-map ^Tile$TileCell cell]
(assoc! cell-map
(cell-index (.getX cell) (.getY cell))
(->Tile (.getX cell)
(.getY cell)
(.getTile cell)
(if (= (.getHFlip cell) 1) true false)
(if (= (.getVFlip cell) 1) true false)
(if (= (.getRotate90 cell) 1) true false))))

(defn paint-cell!
[cell-map x y tile h-flip v-flip rotate90]
(if tile
(assoc! cell-map (cell-index x y) (->Tile x y tile h-flip v-flip rotate90))
(dissoc! cell-map (cell-index x y))))
[cell-map layer-id x y tile h-flip v-flip rotate90]
; figure out where to best do this once
(TilemapPlugins/init workspace/class-loader)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialising every frame is ofc not necessary. The question is where to put this line of code?

; update the cell-map
; send to plugins
; apply changes from plugins to cell-map
(let [updated-cell-map (if tile
(assoc! cell-map (cell-index x y) (->Tile x y tile h-flip v-flip rotate90))
(dissoc! cell-map (cell-index x y)))
updated-cells-pb (if tile
(TilemapPlugins/onPaintTile x y (persistent! updated-cell-map) layer-id)
(TilemapPlugins/onClearTile x y (persistent! updated-cell-map) layer-id))]
(reduce (fn [map cell]
(update-cell-from-pb map cell))
updated-cell-map
updated-cells-pb)))

(defn make-cell-map
[cells]
[cells layer-id]
(persistent! (reduce (fn [ret {:keys [x y tile h-flip v-flip rotate90] :or {h-flip 0 v-flip 0} :as cell}]
(paint-cell! ret x y tile (not= 0 h-flip) (not= 0 v-flip) (not= 0 rotate90)))
(paint-cell! ret layer-id x y tile (not= 0 h-flip) (not= 0 v-flip) (not= 0 rotate90)))
(transient (int-map/int-map))
cells)))

(defn paint
[cell-map [sx sy] brush]
[cell-map layer-id [sx sy] brush]
(let [{:keys [width height tiles]} brush]
(let [ex (+ sx width)
ey (+ sy height)]
Expand All @@ -109,7 +133,7 @@
(if (< y ey)
(if (< x ex)
(let [{:keys [tile h-flip v-flip rotate90]} (first tiles)]
(recur (inc x) y (rest tiles) (paint-cell! cell-map x y tile h-flip v-flip rotate90)))
(recur (inc x) y (rest tiles) (paint-cell! cell-map layer-id x y tile h-flip v-flip rotate90)))
(recur sx (inc y) tiles cell-map))
(persistent! cell-map))))))

Expand Down Expand Up @@ -356,7 +380,7 @@
[layer-node [LayerNode {:id (:id tile-layer)
:z (:z tile-layer)
:visible (not= 0 (:is-visible tile-layer))
:cell-map (make-cell-map (:cell tile-layer))}]]
:cell-map (make-cell-map (:cell tile-layer) (:id tile-layer))}]]
(attach-layer-node parent layer-node))))


Expand Down Expand Up @@ -967,11 +991,12 @@
(when-let [active-layer (g/node-value self :active-layer evaluation-context)]
(when-let [current-tile (g/node-value self :current-tile evaluation-context)]
(let [brush (g/node-value self :brush evaluation-context)
op-seq (gensym)]
op-seq (gensym)
layer-id (g/node-value active-layer :id evaluation-context)]
(swap! state assoc :last-tile current-tile)
[(g/set-property self :op-seq op-seq)
(g/operation-sequence op-seq)
(g/update-property active-layer :cell-map paint current-tile brush)]))))
(g/update-property active-layer :cell-map paint layer-id current-tile brush)]))))

(defmethod update-op :paint
[op self action state evaluation-context]
Expand All @@ -980,9 +1005,10 @@
(when (not= current-tile (-> state deref :last-tile))
(swap! state assoc :last-tile current-tile)
(let [brush (g/node-value self :brush evaluation-context)
op-seq (g/node-value self :op-seq evaluation-context)]
op-seq (g/node-value self :op-seq evaluation-context)
layer-id (g/node-value active-layer :id evaluation-context)]
[(g/operation-sequence op-seq)
(g/update-property active-layer :cell-map paint current-tile brush)])))))
(g/update-property active-layer :cell-map paint layer-id current-tile brush)])))))

(defmethod end-op :paint
[op self action state evaluation-context]
Expand Down
2 changes: 1 addition & 1 deletion editor/test/integration/tile_map_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
brush-tile (some (comp (juxt :x :y) val) cell-map)
brush (tile-map/make-brush-from-selection cell-map brush-tile brush-tile)
cell-map' (reduce (fn [cell-map' pos]
(tile-map/paint cell-map' pos brush))
(tile-map/paint cell-map' layer-id pos brush))
cell-map
(take 128 (partition 2 (repeatedly #(rand-int 64)))))]
(is (= (map val (sort-by key cell-map'))
Expand Down