<commit_before>//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.miso.data;


import java.util.ArrayList;
import java.util.Iterator;

import java.awt.Rectangle;

import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;

import com.threerings.io.SimpleStreamableObject;

import com.threerings.util.StreamableHashIntMap;

import com.threerings.media.util.MathUtil;

import com.threerings.miso.util.ObjectSet;

import static com.threerings.miso.Log.log;

/**
 * Contains miso scene data that is broken up into NxN tile sections.
 */
public class SparseMisoSceneModel extends MisoSceneModel
{
    /** An interface that allows external entities to "visit" and inspect
     * every object in this scene. */
    public static interface ObjectVisitor
    {
        /** Called for each object in the scene, interesting and not. */
        public void visit (ObjectInfo info);
    }

    /** Contains information on a section of this scene. This is only
     * public so that the scene model parser can do its job, so don't go
     * poking around in here. */
    public static class Section extends SimpleStreamableObject
        implements Cloneable
    {
        /** The tile coordinate of our upper leftmost tile. */
        public short x, y;

        /** The width of this section in tiles. */
        public int width;

        /** The combined tile ids (tile set id and tile id) for our
         * section (in row major order). */
        public int[] baseTileIds;

        /** The combined tile ids (tile set id and tile id) of the
         * "uninteresting" tiles in the object layer. */
        public int[] objectTileIds = new int[0];

        /** The x coordinate of the "uninteresting" tiles in the object
         * layer. */
        public short[] objectXs = new short[0];

        /** The y coordinate of the "uninteresting" tiles in the object
         * layer. */
        public short[] objectYs = new short[0];

        /** Information records for the "interesting" objects in the
         * object layer. */
        public ObjectInfo[] objectInfo = new ObjectInfo[0];

        /**
         * Creates a blank section instance, suitable for unserialization
         * or configuration by the XML scene parser.
         */
        public Section ()
        {
        }

        /**
         * Creates a new scene section with the specified dimensions.
         */
        public Section (short x, short y, short width, short height)
        {
            this.x = x;
            this.y = y;
            this.width = width;
            baseTileIds = new int[width*height];
        }

        public int getBaseTileId (int col, int row) {
            if (col < x || col >= (x+width) || row < y || row >= (y+width)) {
                log.warning("Requested bogus tile +" + col + "+" + row +
                            " from " + this + ".");
                return -1;
            } else {
                return baseTileIds[(row-y)*width+(col-x)];
            }
        }

        public void setBaseTile (int col, int row, int fqBaseTileId) {
            baseTileIds[(row-y)*width+(col-x)] = fqBaseTileId;
        }

        public boolean addObject (ObjectInfo info) {
            // sanity check: see if there is already an object of this
            // type at these coordinates
            int dupidx;
            if ((dupidx = ListUtil.indexOf(objectInfo, info)) != -1) {
                log.warning("Refusing to add duplicate object [ninfo=" + info +
                        ", oinfo=" + objectInfo[dupidx] + "].");
                return false;
            }
            if ((dupidx = indexOfUn(info)) != -1) {
                log.warning("Refusing to add duplicate object " +
                        "[info=" + info + "].");
                return false;
            }

            if (info.isInteresting()) {
                objectInfo = ArrayUtil.append(objectInfo, info);
            } else {
                objectTileIds = ArrayUtil.append(objectTileIds, info.tileId);
                objectXs = ArrayUtil.append(objectXs, (short)info.x);
                objectYs = ArrayUtil.append(objectYs, (short)info.y);
            }
            return true;
        }

        public boolean removeObject (ObjectInfo info) {
            // look for it in the interesting info array
            int oidx = ListUtil.indexOf(objectInfo, info);
            if (oidx != -1) {
                objectInfo = ArrayUtil.splice(objectInfo, oidx, 1);
                return true;
            }

            // look for it in the uninteresting arrays
            oidx = indexOfUn(info);
            if (oidx != -1) {
                objectTileIds = ArrayUtil.splice(objectTileIds, oidx, 1);
                objectXs = ArrayUtil.splice(objectXs, oidx, 1);
                objectYs = ArrayUtil.splice(objectYs, oidx, 1);
                return true;
            }

            return false;
        }

        /**
         * Returns the index of the specified object in the uninteresting
         * arrays or -1 if it is not in this section as an uninteresting
         * object.
         */
        protected int indexOfUn (ObjectInfo info)
        {
            for (int ii = 0; ii < objectTileIds.length; ii++) {
                if (objectTileIds[ii] == info.tileId &&
                    objectXs[ii] == info.x && objectYs[ii] == info.y) {
                    return ii;
                }
            }
            return -1;
        }

        public void getAllObjects (ArrayList<ObjectInfo> list) {
            for (ObjectInfo info : objectInfo) {
                list.add(info);
            }
            for (int ii = 0; ii < objectTileIds.length; ii++) {
                int x = objectXs[ii], y = objectYs[ii];
                list.add(new ObjectInfo(objectTileIds[ii], x, y));
            }
        }

        public void getObjects (Rectangle region, ObjectSet set) {
            // first look for intersecting interesting objects
            for (int ii = 0; ii < objectInfo.length; ii++) {
                ObjectInfo info = objectInfo[ii];
                if (region.contains(info.x, info.y)) {
                    set.insert(info);
                }
            }

            // now look for intersecting non-interesting objects
            for (int ii = 0; ii < objectTileIds.length; ii++) {
                int x = objectXs[ii], y = objectYs[ii];
                if (region.contains(x, y)) {
                    set.insert(new ObjectInfo(objectTileIds[ii], x, y));
                }
            }
        }

        /**
         * Returns true if this section contains no data beyond the default.
         * Used when saving a sparse scene: we omit blank sections.
         */
        public boolean isBlank ()
        {
            if ((objectTileIds.length != 0) || (objectInfo.length != 0)) {
                return false;
            }
            for (int ii=0, nn=baseTileIds.length; ii < nn; ii++) {
                if (baseTileIds[ii] != 0) {
                    return false;
                }
            }

            return true;
        }

        @Override
        public Object clone () {
            try {
                Section section = (Section)super.clone();
                section.baseTileIds = baseTileIds.clone();
                section.objectTileIds = objectTileIds.clone();
                section.objectXs = objectXs.clone();
                section.objectYs = objectYs.clone();
                section.objectInfo = objectInfo.clone();
                return section;
            } catch (CloneNotSupportedException cnse) {
                throw new RuntimeException(cnse);
            }
        }

        @Override
        public String toString () {
            return ((width == 0) ? "<no bounds>" :
                    (width + "x" + (baseTileIds.length/width))) +
                "+" + x + "+" + y +
                ":" + objectInfo.length + ":" + objectTileIds.length;
        }
    }

    /** The dimensions of a section of our scene. */
    public short swidth, sheight;

    /** The tileset to use when we have no tile data. */
    public int defTileSet = 0;

    /**
     * Creates a scene model with the specified bounds.
     *
     * @param swidth the width of a single section (in tiles).
     * @param sheight the height of a single section (in tiles).
     */
    public SparseMisoSceneModel (int swidth, int sheight)
    {
        this.swidth = (short)swidth;
        this.sheight = (short)sheight;
    }

    /**
     * Creates a blank model suitable for unserialization.
     */
    public SparseMisoSceneModel ()
    {
    }

    /**
     * Adds all interesting {@link ObjectInfo} records in this scene to
     * the supplied list.
     */
    public void getInterestingObjects (ArrayList<ObjectInfo> list)
    {
        for (Iterator<Section> iter = getSections(); iter.hasNext(); ) {
            Section sect = iter.next();
            for (int oo = 0; oo < sect.objectInfo.length; oo++) {
                list.add(sect.objectInfo[oo]);
            }
        }
    }

    /**
     * Adds all {@link ObjectInfo} records in this scene to the supplied list.
     */
    public void getAllObjects (ArrayList<ObjectInfo> list)
    {
        for (Iterator<Section> iter = getSections(); iter.hasNext(); ) {
            iter.next().getAllObjects(list);
        }
    }

    /**
     * Informs the supplied visitor of each object in this scene.
     */
    public void visitObjects (ObjectVisitor visitor)
    {
        visitObjects(visitor, false);
    }

    /**
     * Informs the supplied visitor of each object in this scene.
     *
     * @param interestingOnly if true, only the interesting objects will
     * be visited.
     */
    public void visitObjects (ObjectVisitor visitor, boolean interestingOnly)
    {
        for (Iterator<Section> iter = getSections(); iter.hasNext(); ) {
            Section sect = iter.next();
            for (int oo = 0; oo < sect.objectInfo.length; oo++) {
                ObjectInfo oinfo = sect.objectInfo[oo];
                visitor.visit(oinfo);
            }
            if (!interestingOnly) {
                ObjectInfo info = new ObjectInfo();
                for (int oo = 0; oo < sect.objectTileIds.length; oo++) {
                    info.tileId = sect.objectTileIds[oo];
                    info.x = sect.objectXs[oo];
                    info.y = sect.objectYs[oo];
                    visitor.visit(info);
                }
            }
        }<commit_msg>Skip on toString if baseTileIds is null in addition to when width is 0.  When digester is logging - which happens when log4j is added to the classpath but not configured - it prints Sections out while they're being initialized and have a width but no baseTileIds.<commit_after>//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.miso.data;


import java.util.ArrayList;
import java.util.Iterator;

import java.awt.Rectangle;

import com.samskivert.util.ArrayUtil;
import com.samskivert.util.ListUtil;
import com.samskivert.util.StringUtil;

import com.threerings.io.SimpleStreamableObject;

import com.threerings.util.StreamableHashIntMap;

import com.threerings.media.util.MathUtil;

import com.threerings.miso.util.ObjectSet;

import static com.threerings.miso.Log.log;

/**
 * Contains miso scene data that is broken up into NxN tile sections.
 */
public class SparseMisoSceneModel extends MisoSceneModel
{
    /** An interface that allows external entities to "visit" and inspect
     * every object in this scene. */
    public static interface ObjectVisitor
    {
        /** Called for each object in the scene, interesting and not. */
        public void visit (ObjectInfo info);
    }

    /** Contains information on a section of this scene. This is only
     * public so that the scene model parser can do its job, so don't go
     * poking around in here. */
    public static class Section extends SimpleStreamableObject
        implements Cloneable
    {
        /** The tile coordinate of our upper leftmost tile. */
        public short x, y;

        /** The width of this section in tiles. */
        public int width;

        /** The combined tile ids (tile set id and tile id) for our
         * section (in row major order). */
        public int[] baseTileIds;

        /** The combined tile ids (tile set id and tile id) of the
         * "uninteresting" tiles in the object layer. */
        public int[] objectTileIds = new int[0];

        /** The x coordinate of the "uninteresting" tiles in the object
         * layer. */
        public short[] objectXs = new short[0];

        /** The y coordinate of the "uninteresting" tiles in the object
         * layer. */
        public short[] objectYs = new short[0];

        /** Information records for the "interesting" objects in the
         * object layer. */
        public ObjectInfo[] objectInfo = new ObjectInfo[0];

        /**
         * Creates a blank section instance, suitable for unserialization
         * or configuration by the XML scene parser.
         */
        public Section ()
        {
        }

        /**
         * Creates a new scene section with the specified dimensions.
         */
        public Section (short x, short y, short width, short height)
        {
            this.x = x;
            this.y = y;
            this.width = width;
            baseTileIds = new int[width*height];
        }

        public int getBaseTileId (int col, int row) {
            if (col < x || col >= (x+width) || row < y || row >= (y+width)) {
                log.warning("Requested bogus tile +" + col + "+" + row +
                            " from " + this + ".");
                return -1;
            } else {
                return baseTileIds[(row-y)*width+(col-x)];
            }
        }

        public void setBaseTile (int col, int row, int fqBaseTileId) {
            baseTileIds[(row-y)*width+(col-x)] = fqBaseTileId;
        }

        public boolean addObject (ObjectInfo info) {
            // sanity check: see if there is already an object of this
            // type at these coordinates
            int dupidx;
            if ((dupidx = ListUtil.indexOf(objectInfo, info)) != -1) {
                log.warning("Refusing to add duplicate object [ninfo=" + info +
                        ", oinfo=" + objectInfo[dupidx] + "].");
                return false;
            }
            if ((dupidx = indexOfUn(info)) != -1) {
                log.warning("Refusing to add duplicate object " +
                        "[info=" + info + "].");
                return false;
            }

            if (info.isInteresting()) {
                objectInfo = ArrayUtil.append(objectInfo, info);
            } else {
                objectTileIds = ArrayUtil.append(objectTileIds, info.tileId);
                objectXs = ArrayUtil.append(objectXs, (short)info.x);
                objectYs = ArrayUtil.append(objectYs, (short)info.y);
            }
            return true;
        }

        public boolean removeObject (ObjectInfo info) {
            // look for it in the interesting info array
            int oidx = ListUtil.indexOf(objectInfo, info);
            if (oidx != -1) {
                objectInfo = ArrayUtil.splice(objectInfo, oidx, 1);
                return true;
            }

            // look for it in the uninteresting arrays
            oidx = indexOfUn(info);
            if (oidx != -1) {
                objectTileIds = ArrayUtil.splice(objectTileIds, oidx, 1);
                objectXs = ArrayUtil.splice(objectXs, oidx, 1);
                objectYs = ArrayUtil.splice(objectYs, oidx, 1);
                return true;
            }

            return false;
        }

        /**
         * Returns the index of the specified object in the uninteresting
         * arrays or -1 if it is not in this section as an uninteresting
         * object.
         */
        protected int indexOfUn (ObjectInfo info)
        {
            for (int ii = 0; ii < objectTileIds.length; ii++) {
                if (objectTileIds[ii] == info.tileId &&
                    objectXs[ii] == info.x && objectYs[ii] == info.y) {
                    return ii;
                }
            }
            return -1;
        }

        public void getAllObjects (ArrayList<ObjectInfo> list) {
            for (ObjectInfo info : objectInfo) {
                list.add(info);
            }
            for (int ii = 0; ii < objectTileIds.length; ii++) {
                int x = objectXs[ii], y = objectYs[ii];
                list.add(new ObjectInfo(objectTileIds[ii], x, y));
            }
        }

        public void getObjects (Rectangle region, ObjectSet set) {
            // first look for intersecting interesting objects
            for (ObjectInfo info : objectInfo) {
                if (region.contains(info.x, info.y)) {
                    set.insert(info);
                }
            }

            // now look for intersecting non-interesting objects
            for (int ii = 0; ii < objectTileIds.length; ii++) {
                int x = objectXs[ii], y = objectYs[ii];
                if (region.contains(x, y)) {
                    set.insert(new ObjectInfo(objectTileIds[ii], x, y));
                }
            }
        }

        /**
         * Returns true if this section contains no data beyond the default.
         * Used when saving a sparse scene: we omit blank sections.
         */
        public boolean isBlank ()
        {
            if ((objectTileIds.length != 0) || (objectInfo.length != 0)) {
                return false;
            }
            for (int baseTileId : baseTileIds) {
                if (baseTileId != 0) {
                    return false;
                }
            }

            return true;
        }

        @Override
        public Object clone () {
            try {
                Section section = (Section)super.clone();
                section.baseTileIds = baseTileIds.clone();
                section.objectTileIds = objectTileIds.clone();
                section.objectXs = objectXs.clone();
                section.objectYs = objectYs.clone();
                section.objectInfo = objectInfo.clone();
                return section;
            } catch (CloneNotSupportedException cnse) {
                throw new RuntimeException(cnse);
            }
        }

        @Override
        public String toString () {
            if (width == 0 || baseTileIds == null) {
                return "<no bounds>";
            } else {
                return String.format("%sx%s+%s:%s:%s", width, baseTileIds.length / width, x, y,
                    objectInfo.length, objectTileIds.length);
            }
        }
    }

    /** The dimensions of a section of our scene. */
    public short swidth, sheight;

    /** The tileset to use when we have no tile data. */
    public int defTileSet = 0;

    /**
     * Creates a scene model with the specified bounds.
     *
     * @param swidth the width of a single section (in tiles).
     * @param sheight the height of a single section (in tiles).
     */
    public SparseMisoSceneModel (int swidth, int sheight)
    {
        this.swidth = (short)swidth;
        this.sheight = (short)sheight;
    }

    /**
     * Creates a blank model suitable for unserialization.
     */
    public SparseMisoSceneModel ()
    {
    }

    /**
     * Adds all interesting {@link ObjectInfo} records in this scene to
     * the supplied list.
     */
    public void getInterestingObjects (ArrayList<ObjectInfo> list)
    {
        for (Iterator<Section> iter = getSections(); iter.hasNext(); ) {
            Section sect = iter.next();
            for (ObjectInfo element : sect.objectInfo) {
                list.add(element);
            }
        }
    }

    /**
     * Adds all {@link ObjectInfo} records in this scene to the supplied list.
     */
    public void getAllObjects (ArrayList<ObjectInfo> list)
    {
        for (Iterator<Section> iter = getSections(); iter.hasNext(); ) {
            iter.next().getAllObjects(list);
        }
    }

    /**
     * Informs the supplied visitor of each object in this scene.
     */
    public void visitObjects (ObjectVisitor visitor)
    {
        visitObjects(visitor, false);
    }

    /**
     * Informs the supplied visitor of each object in this scene.
     *
     * @param interestingOnly if true, only the interesting objects will
     * be visited.
     */
    public void visitObjects (ObjectVisitor visitor, boolean interestingOnly)
    {
        for (Iterator<Section> iter = getSections(); iter.hasNext(); ) {
            Section sect = iter.next();
            for (ObjectInfo oinfo : sect.objectInfo) {
                visitor.visit(oinfo);
            }
            if (!interestingOnly) {
                ObjectInfo info = new ObjectInfo();
                for (int oo = 0; oo < sect.objectTileIds.length; oo++) {
                    info.tileId = sect.objectTileIds[oo];
                    info.x = sect.objectXs[oo];
                    info.y = sect.objectYs[oo];
                    visitor.visit(info);
                }
            }
        }
{'fraction_non_alphanumeric': 0.09910419203339711, 'fraction_numerical': 0.004435554009392938, 'mean_word_length': 1.6975953079178885, 'pattern_counts': {'":': 2, '<': 33, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3017521', 'n_tokens_mistral': 6285, 'n_tokens_neox': 6008, 'n_words': 2018}