mirror of
https://github.com/harfbuzz/harfbuzz.git
synced 2025-04-13 08:42:59 +00:00
Merge pull request #5074 from harfbuzz/tortoise-hare
[decycler] Implement an efficient graph cycle detector
This commit is contained in:
commit
4c263ecd00
8 changed files with 328 additions and 56 deletions
|
@ -29,6 +29,7 @@
|
|||
#define OT_COLOR_COLR_COLR_HH
|
||||
|
||||
#include "../../../hb.hh"
|
||||
#include "../../../hb-decycler.hh"
|
||||
#include "../../../hb-open-type.hh"
|
||||
#include "../../../hb-ot-var-common.hh"
|
||||
#include "../../../hb-paint.hh"
|
||||
|
@ -71,8 +72,8 @@ public:
|
|||
hb_array_t<const BGRAColor> palette;
|
||||
hb_color_t foreground;
|
||||
ItemVarStoreInstancer &instancer;
|
||||
hb_map_t current_glyphs;
|
||||
hb_map_t current_layers;
|
||||
hb_decycler_t glyphs_decycler;
|
||||
hb_decycler_t layers_decycler;
|
||||
int depth_left = HB_MAX_NESTING_LEVEL;
|
||||
int edge_count = HB_MAX_GRAPH_EDGE_COUNT;
|
||||
|
||||
|
@ -2580,7 +2581,9 @@ struct COLR
|
|||
&(get_delta_set_index_map ()),
|
||||
hb_array (font->coords, font->num_coords));
|
||||
hb_paint_context_t c (this, funcs, data, font, palette_index, foreground, instancer);
|
||||
c.current_glyphs.add (glyph);
|
||||
|
||||
hb_decycler_node_t node (c.glyphs_decycler);
|
||||
node.visit (glyph);
|
||||
|
||||
if (version >= 1)
|
||||
{
|
||||
|
@ -2696,19 +2699,16 @@ void PaintColrLayers::paint_glyph (hb_paint_context_t *c) const
|
|||
{
|
||||
TRACE_PAINT (this);
|
||||
const LayerList &paint_offset_lists = c->get_colr_table ()->get_layerList ();
|
||||
hb_decycler_node_t node (c->layers_decycler);
|
||||
for (unsigned i = firstLayerIndex; i < firstLayerIndex + numLayers; i++)
|
||||
{
|
||||
if (unlikely (c->current_layers.has (i)))
|
||||
continue;
|
||||
|
||||
c->current_layers.add (i);
|
||||
if (unlikely (!node.visit (i)))
|
||||
return;
|
||||
|
||||
const Paint &paint = paint_offset_lists.get_paint (i);
|
||||
c->funcs->push_group (c->data);
|
||||
c->recurse (paint);
|
||||
c->funcs->pop_group (c->data, HB_PAINT_COMPOSITE_MODE_SRC_OVER);
|
||||
|
||||
c->current_layers.del (i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2716,16 +2716,14 @@ void PaintColrGlyph::paint_glyph (hb_paint_context_t *c) const
|
|||
{
|
||||
TRACE_PAINT (this);
|
||||
|
||||
if (unlikely (c->current_glyphs.has (gid)))
|
||||
hb_decycler_node_t node (c->glyphs_decycler);
|
||||
if (unlikely (!node.visit (gid)))
|
||||
return;
|
||||
|
||||
c->current_glyphs.add (gid);
|
||||
|
||||
c->funcs->push_inverse_root_transform (c->data, c->font);
|
||||
if (c->funcs->color_glyph (c->data, gid, c->font))
|
||||
{
|
||||
c->funcs->pop_transform (c->data);
|
||||
c->current_glyphs.del (gid);
|
||||
return;
|
||||
}
|
||||
c->funcs->pop_transform (c->data);
|
||||
|
@ -2748,8 +2746,6 @@ void PaintColrGlyph::paint_glyph (hb_paint_context_t *c) const
|
|||
|
||||
if (has_clip_box)
|
||||
c->funcs->pop_clip (c->data);
|
||||
|
||||
c->current_glyphs.del (gid);
|
||||
}
|
||||
|
||||
} /* namespace OT */
|
||||
|
|
|
@ -134,7 +134,7 @@ VarComponent::get_path_at (hb_font_t *font,
|
|||
hb_array_t<const int> coords,
|
||||
hb_transform_t total_transform,
|
||||
hb_ubytes_t total_record,
|
||||
hb_set_t *visited,
|
||||
hb_decycler_t *decycler,
|
||||
signed *edges_left,
|
||||
signed depth_left,
|
||||
VarRegionList::cache_t *cache) const
|
||||
|
@ -319,7 +319,7 @@ VarComponent::get_path_at (hb_font_t *font,
|
|||
VARC.get_path_at (font, gid,
|
||||
draw_session, component_coords, total_transform,
|
||||
parent_gid,
|
||||
visited, edges_left, depth_left - 1);
|
||||
decycler, edges_left, depth_left - 1);
|
||||
}
|
||||
|
||||
#undef PROCESS_TRANSFORM_COMPONENTS
|
||||
|
@ -335,17 +335,10 @@ VARC::get_path_at (hb_font_t *font,
|
|||
hb_array_t<const int> coords,
|
||||
hb_transform_t transform,
|
||||
hb_codepoint_t parent_glyph,
|
||||
hb_set_t *visited,
|
||||
hb_decycler_t *decycler,
|
||||
signed *edges_left,
|
||||
signed depth_left) const
|
||||
{
|
||||
hb_set_t stack_set;
|
||||
if (visited == nullptr)
|
||||
visited = &stack_set;
|
||||
signed stack_edges = HB_MAX_GRAPH_EDGE_COUNT;
|
||||
if (edges_left == nullptr)
|
||||
edges_left = &stack_edges;
|
||||
|
||||
// Don't recurse on the same glyph.
|
||||
unsigned idx = glyph == parent_glyph ?
|
||||
NOT_COVERED :
|
||||
|
@ -377,9 +370,9 @@ VARC::get_path_at (hb_font_t *font,
|
|||
return true;
|
||||
(*edges_left)--;
|
||||
|
||||
if (visited->has (glyph) || visited->in_error ())
|
||||
hb_decycler_node_t node (*decycler);
|
||||
if (unlikely (!node.visit (glyph)))
|
||||
return true;
|
||||
visited->add (glyph);
|
||||
|
||||
hb_ubytes_t record = (this+glyphRecords)[idx];
|
||||
|
||||
|
@ -392,13 +385,11 @@ VARC::get_path_at (hb_font_t *font,
|
|||
VarCompositeGlyph::get_path_at (font, glyph,
|
||||
draw_session, coords, transform,
|
||||
record,
|
||||
visited, edges_left, depth_left,
|
||||
decycler, edges_left, depth_left,
|
||||
cache);
|
||||
|
||||
(this+varStore).destroy_cache (cache);
|
||||
|
||||
visited->del (glyph);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef OT_VAR_VARC_VARC_HH
|
||||
#define OT_VAR_VARC_VARC_HH
|
||||
|
||||
#include "../../../hb-decycler.hh"
|
||||
#include "../../../hb-geometry.hh"
|
||||
#include "../../../hb-ot-layout-common.hh"
|
||||
#include "../../../hb-ot-glyf-table.hh"
|
||||
|
@ -49,7 +50,7 @@ struct VarComponent
|
|||
hb_array_t<const int> coords,
|
||||
hb_transform_t transform,
|
||||
hb_ubytes_t record,
|
||||
hb_set_t *visited,
|
||||
hb_decycler_t *decycler,
|
||||
signed *edges_left,
|
||||
signed depth_left,
|
||||
VarRegionList::cache_t *cache = nullptr) const;
|
||||
|
@ -64,7 +65,7 @@ struct VarCompositeGlyph
|
|||
hb_array_t<const int> coords,
|
||||
hb_transform_t transform,
|
||||
hb_ubytes_t record,
|
||||
hb_set_t *visited,
|
||||
hb_decycler_t *decycler,
|
||||
signed *edges_left,
|
||||
signed depth_left,
|
||||
VarRegionList::cache_t *cache = nullptr)
|
||||
|
@ -75,7 +76,7 @@ struct VarCompositeGlyph
|
|||
record = comp.get_path_at (font, glyph,
|
||||
draw_session, coords, transform,
|
||||
record,
|
||||
visited, edges_left, depth_left, cache);
|
||||
decycler, edges_left, depth_left, cache);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -93,15 +94,27 @@ struct VARC
|
|||
hb_codepoint_t glyph,
|
||||
hb_draw_session_t &draw_session,
|
||||
hb_array_t<const int> coords,
|
||||
hb_transform_t transform = HB_TRANSFORM_IDENTITY,
|
||||
hb_codepoint_t parent_glyph = HB_CODEPOINT_INVALID,
|
||||
hb_set_t *visited = nullptr,
|
||||
signed *edges_left = nullptr,
|
||||
signed depth_left = HB_MAX_NESTING_LEVEL) const;
|
||||
hb_transform_t transform,
|
||||
hb_codepoint_t parent_glyph,
|
||||
hb_decycler_t *decycler,
|
||||
signed *edges_left,
|
||||
signed depth_left) const;
|
||||
|
||||
bool
|
||||
get_path (hb_font_t *font, hb_codepoint_t gid, hb_draw_session_t &draw_session) const
|
||||
{ return get_path_at (font, gid, draw_session, hb_array (font->coords, font->num_coords)); }
|
||||
{
|
||||
hb_decycler_t decycler;
|
||||
signed edges = HB_MAX_GRAPH_EDGE_COUNT;
|
||||
|
||||
return get_path_at (font,
|
||||
gid,
|
||||
draw_session,
|
||||
hb_array (font->coords, font->num_coords),
|
||||
HB_TRANSFORM_IDENTITY,
|
||||
HB_CODEPOINT_INVALID,
|
||||
&decycler,
|
||||
&edges,
|
||||
HB_MAX_NESTING_LEVEL); }
|
||||
|
||||
bool paint_glyph (hb_font_t *font, hb_codepoint_t gid, hb_paint_funcs_t *funcs, void *data, hb_color_t foreground) const
|
||||
{
|
||||
|
|
161
src/hb-decycler.hh
Normal file
161
src/hb-decycler.hh
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright © 2025 Behdad Esfahbod
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Author(s): Behdad Esfahbod
|
||||
*/
|
||||
|
||||
#ifndef HB_DECYCLER_HH
|
||||
#define HB_DECYCLER_HH
|
||||
|
||||
#include "hb.hh"
|
||||
|
||||
/*
|
||||
* hb_decycler_t is an efficient cycle detector for graph traversal.
|
||||
* It's a simple tortoise-and-hare algorithm with a twist: it's
|
||||
* designed to detect cycles while traversing a graph in a DFS manner,
|
||||
* instead of just a linked list.
|
||||
*
|
||||
* For Floyd's tortoise and hare algorithm, see:
|
||||
* https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare
|
||||
*
|
||||
* Like Floyd's algorithm, hb_decycler_t is O(n) in the number of nodes
|
||||
* in the graph. Unlike Floyd's algorithm, hb_decycler_t is designed
|
||||
* to be used in a DFS traversal, where the graph is not a simple
|
||||
* linked list, but a tree with cycles. Like Floyd's algorithm, it is
|
||||
* constant-memory (just two pointers).
|
||||
*
|
||||
* The decycler works by creating an implicit linked-list on the stack,
|
||||
* of the path from the root to the current node, and apply Floyd's
|
||||
* algorithm on that list as it goes.
|
||||
*
|
||||
* The decycler is malloc-free, and as such, much faster to use than a
|
||||
* hb_set_t or hb_map_t equivalent.
|
||||
*
|
||||
* The decycler detects cycles in the graph *eventually*, not *immediately*.
|
||||
* That is, it may not detect a cycle until the cycle is fully traversed,
|
||||
* even multiple times. See Floyd's algorithm analysis for details.
|
||||
*
|
||||
* The implementation saves a pointer storage on the stack by combining
|
||||
* this->u.decycler and this->u.next into a union. This is possible because
|
||||
* at any point we only need one of those values. The invariant is that
|
||||
* after construction, and before destruction, of a node, the u.decycler
|
||||
* field is always valid. The u.next field is only valid when the node is
|
||||
* in the traversal path, parent to another node.
|
||||
*
|
||||
* There are three method's:
|
||||
*
|
||||
* - hb_decycler_node_t() constructor: Creates a new node in the traversal.
|
||||
* The constructor takes a reference to the decycler object and inserts
|
||||
* itself as the latest node in the traversal path, by advancing the hare
|
||||
* pointer, and for every other descent, advancing the tortoise pointer.
|
||||
*
|
||||
* - ~hb_decycler_node_t() destructor: Restores the decycler object to its
|
||||
* previous state by removing the node from the traversal path.
|
||||
*
|
||||
* - bool visit(uintptr_t value): Called on every node in the graph. Returns
|
||||
* true if the node is not part of a cycle, and false if it is. The value
|
||||
* parameter is used to detect cycles. It's the caller's responsibility
|
||||
* to ensure that the value is unique for each node in the graph.
|
||||
* The cycle detection is as simple as comparing the value to the value
|
||||
* held by the tortoise pointer, which is the Floyd's algorithm.
|
||||
*
|
||||
* For usage examples see test-decycler.cc.
|
||||
*/
|
||||
|
||||
struct hb_decycler_node_t;
|
||||
|
||||
struct hb_decycler_t
|
||||
{
|
||||
friend struct hb_decycler_node_t;
|
||||
|
||||
private:
|
||||
bool tortoise_asleep = true;
|
||||
hb_decycler_node_t *tortoise = nullptr;
|
||||
hb_decycler_node_t *hare = nullptr;
|
||||
};
|
||||
|
||||
struct hb_decycler_node_t
|
||||
{
|
||||
hb_decycler_node_t (hb_decycler_t &decycler)
|
||||
{
|
||||
u.decycler = &decycler;
|
||||
|
||||
decycler.tortoise_asleep = !decycler.tortoise_asleep;
|
||||
|
||||
if (!decycler.tortoise)
|
||||
{
|
||||
// First node.
|
||||
decycler.tortoise = decycler.hare = this;
|
||||
return;
|
||||
}
|
||||
if (!decycler.tortoise_asleep)
|
||||
decycler.tortoise = decycler.tortoise->u.next; // Time to move.
|
||||
|
||||
this->prev = decycler.hare;
|
||||
decycler.hare->u.next = this;
|
||||
decycler.hare = this;
|
||||
}
|
||||
|
||||
~hb_decycler_node_t ()
|
||||
{
|
||||
hb_decycler_t &decycler = *u.decycler;
|
||||
|
||||
// Inverse of the constructor.
|
||||
|
||||
assert (decycler.hare == this);
|
||||
decycler.hare = prev;
|
||||
if (prev)
|
||||
prev->u.decycler = &decycler;
|
||||
|
||||
assert (decycler.tortoise);
|
||||
if (!decycler.tortoise_asleep)
|
||||
decycler.tortoise = decycler.tortoise->prev;
|
||||
|
||||
decycler.tortoise_asleep = !decycler.tortoise_asleep;
|
||||
}
|
||||
|
||||
bool visit (uintptr_t value_)
|
||||
{
|
||||
value = value_;
|
||||
|
||||
hb_decycler_t &decycler = *u.decycler;
|
||||
|
||||
if (decycler.tortoise == this)
|
||||
return true; // First node; not a cycle.
|
||||
|
||||
if (decycler.tortoise->value == value)
|
||||
return false; // Cycle detected.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
union {
|
||||
hb_decycler_t *decycler;
|
||||
hb_decycler_node_t *next;
|
||||
} u = {nullptr};
|
||||
hb_decycler_node_t *prev = nullptr;
|
||||
uintptr_t value = 0;
|
||||
};
|
||||
|
||||
#endif /* HB_DECYCLER_HH */
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include "hb.hh"
|
||||
|
||||
#include "hb-decycler.hh"
|
||||
#include "hb-paint-extents.hh"
|
||||
|
||||
#include FT_COLOR_H
|
||||
|
@ -105,8 +106,8 @@ struct hb_ft_paint_context_t
|
|||
FT_Color *palette;
|
||||
unsigned palette_index;
|
||||
hb_color_t foreground;
|
||||
hb_map_t current_glyphs;
|
||||
hb_map_t current_layers;
|
||||
hb_decycler_t glyphs_decycler;
|
||||
hb_decycler_t layers_decycler;
|
||||
int depth_left = HB_MAX_NESTING_LEVEL;
|
||||
int edge_count = HB_MAX_GRAPH_EDGE_COUNT;
|
||||
};
|
||||
|
@ -218,24 +219,19 @@ _hb_ft_paint (hb_ft_paint_context_t *c,
|
|||
case FT_COLR_PAINTFORMAT_COLR_LAYERS:
|
||||
{
|
||||
FT_OpaquePaint other_paint = {0};
|
||||
hb_decycler_node_t node (c->layers_decycler);
|
||||
while (FT_Get_Paint_Layers (ft_face,
|
||||
&paint.u.colr_layers.layer_iterator,
|
||||
&other_paint))
|
||||
{
|
||||
// FreeType doesn't provide a way to get the layer index, so we use the pointer
|
||||
// for cycle detection.
|
||||
unsigned i = (unsigned) (uintptr_t) other_paint.p;
|
||||
|
||||
if (unlikely (c->current_layers.has (i)))
|
||||
if (unlikely (!node.visit ((uintptr_t) other_paint.p)))
|
||||
continue;
|
||||
|
||||
c->current_layers.add (i);
|
||||
|
||||
c->funcs->push_group (c->data);
|
||||
c->recurse (other_paint);
|
||||
c->funcs->pop_group (c->data, HB_PAINT_COMPOSITE_MODE_SRC_OVER);
|
||||
|
||||
c->current_layers.del (i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -335,18 +331,16 @@ _hb_ft_paint (hb_ft_paint_context_t *c,
|
|||
{
|
||||
hb_codepoint_t gid = paint.u.colr_glyph.glyphID;
|
||||
|
||||
if (unlikely (c->current_glyphs.has (gid)))
|
||||
hb_decycler_node_t node (c->glyphs_decycler);
|
||||
if (unlikely (!node.visit (gid)))
|
||||
return;
|
||||
|
||||
c->current_glyphs.add (gid);
|
||||
|
||||
c->funcs->push_inverse_root_transform (c->data, c->font);
|
||||
c->ft_font->lock.unlock ();
|
||||
if (c->funcs->color_glyph (c->data, gid, c->font))
|
||||
{
|
||||
c->ft_font->lock.lock ();
|
||||
c->funcs->pop_transform (c->data);
|
||||
c->current_glyphs.del (gid);
|
||||
return;
|
||||
}
|
||||
c->ft_font->lock.lock ();
|
||||
|
@ -382,8 +376,6 @@ _hb_ft_paint (hb_ft_paint_context_t *c,
|
|||
|
||||
if (has_clip_box)
|
||||
c->funcs->pop_clip (c->data);
|
||||
|
||||
c->current_glyphs.del (gid);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -508,7 +500,8 @@ hb_ft_paint_glyph_colr (hb_font_t *font,
|
|||
hb_ft_paint_context_t c (ft_font, font,
|
||||
paint_funcs, paint_data,
|
||||
palette, palette_index, foreground);
|
||||
c.current_glyphs.add (gid);
|
||||
hb_decycler_node_t node (c.glyphs_decycler);
|
||||
node.visit (gid);
|
||||
|
||||
bool is_bounded = true;
|
||||
FT_ClipBox clip_box;
|
||||
|
@ -532,7 +525,8 @@ hb_ft_paint_glyph_colr (hb_font_t *font,
|
|||
hb_ft_paint_context_t ce (ft_font, font,
|
||||
extents_funcs, &extents_data,
|
||||
palette, palette_index, foreground);
|
||||
ce.current_glyphs.add (gid);
|
||||
hb_decycler_node_t node2 (ce.glyphs_decycler);
|
||||
node2.visit (gid);
|
||||
ce.funcs->push_root_transform (ce.data, font);
|
||||
ce.recurse (paint);
|
||||
ce.funcs->pop_transform (ce.data);
|
||||
|
|
|
@ -131,6 +131,7 @@
|
|||
#pragma GCC diagnostic ignored "-Wclass-memaccess"
|
||||
#pragma GCC diagnostic ignored "-Wcast-function-type-strict" // https://github.com/harfbuzz/harfbuzz/pull/3859#issuecomment-1295409126
|
||||
#pragma GCC diagnostic ignored "-Wdangling-reference" // https://github.com/harfbuzz/harfbuzz/issues/4043
|
||||
#pragma GCC diagnostic ignored "-Wdangling-pointer" // Trigerred by hb_decycler_node_t().
|
||||
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
||||
#pragma GCC diagnostic ignored "-Wformat-zero-length"
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
|
|
@ -43,6 +43,7 @@ hb_base_sources = files(
|
|||
'hb-common.cc',
|
||||
'hb-config.hh',
|
||||
'hb-debug.hh',
|
||||
'hb-decycler.hh',
|
||||
'hb-dispatch.hh',
|
||||
'hb-draw.cc',
|
||||
'hb-draw.hh',
|
||||
|
@ -725,6 +726,7 @@ if get_option('tests').enabled()
|
|||
'test-bimap': ['test-bimap.cc', 'hb-static.cc'],
|
||||
'test-cff': ['test-cff.cc', 'hb-static.cc'],
|
||||
'test-classdef-graph': ['graph/test-classdef-graph.cc', 'hb-static.cc', 'graph/gsubgpos-context.cc'],
|
||||
'test-decycler': ['test-decycler.cc', 'hb-static.cc'],
|
||||
'test-iter': ['test-iter.cc', 'hb-static.cc'],
|
||||
'test-machinery': ['test-machinery.cc', 'hb-static.cc'],
|
||||
'test-map': ['test-map.cc', 'hb-static.cc'],
|
||||
|
|
114
src/test-decycler.cc
Normal file
114
src/test-decycler.cc
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright © 2025 Behdad Esfahbod
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Author(s): Behdad Esfahbod
|
||||
*/
|
||||
|
||||
#include "hb.hh"
|
||||
#include "hb-decycler.hh"
|
||||
|
||||
static void
|
||||
tree_recurse_binary (unsigned value,
|
||||
unsigned max_value,
|
||||
hb_decycler_t &decycler)
|
||||
{
|
||||
if (value >= max_value)
|
||||
return;
|
||||
|
||||
hb_decycler_node_t node (decycler);
|
||||
|
||||
bool ret = node.visit (value);
|
||||
assert (ret);
|
||||
|
||||
tree_recurse_binary (value * 2 + 1, max_value, decycler);
|
||||
tree_recurse_binary (value * 2 + 2, max_value, decycler);
|
||||
}
|
||||
|
||||
static void
|
||||
tree_recurse_tertiary (unsigned value,
|
||||
unsigned max_value,
|
||||
hb_decycler_t &decycler)
|
||||
{
|
||||
/* This function implements an alternative way to use the
|
||||
* decycler. It checks for each node before visiting it.
|
||||
* It demonstrates reusing a node for multiple visits. */
|
||||
|
||||
if (value >= max_value)
|
||||
return;
|
||||
|
||||
hb_decycler_node_t node (decycler);
|
||||
|
||||
value *= 3;
|
||||
|
||||
for (unsigned i = 1; i <= 3; i++)
|
||||
{
|
||||
bool ret = node.visit (value + i);
|
||||
assert (ret);
|
||||
|
||||
tree_recurse_tertiary (value + i, max_value, decycler);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_tree ()
|
||||
{
|
||||
hb_decycler_t decycler;
|
||||
tree_recurse_binary (0, 64, decycler);
|
||||
tree_recurse_tertiary (0, 1000, decycler);
|
||||
}
|
||||
|
||||
static void
|
||||
cycle_recurse (signed value,
|
||||
signed cycle_length,
|
||||
hb_decycler_t &decycler)
|
||||
{
|
||||
assert (cycle_length > 0);
|
||||
|
||||
hb_decycler_node_t node (decycler);
|
||||
|
||||
if (!node.visit (value))
|
||||
return;
|
||||
|
||||
if (value >= cycle_length)
|
||||
value = value % cycle_length;
|
||||
|
||||
cycle_recurse (value + 1, cycle_length, decycler);
|
||||
}
|
||||
|
||||
static void
|
||||
test_cycle ()
|
||||
{
|
||||
hb_decycler_t decycler;
|
||||
cycle_recurse (2, 3, decycler);
|
||||
cycle_recurse (-20, 8, decycler);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
test_tree ();
|
||||
test_cycle ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue