From 85adc697d2ec2a379ce6d721f0419ae5df3abdb6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 07:46:17 +0200 Subject: Adding upstream version 2.1.1. Signed-off-by: Daniel Baumann --- include/Makefile.am | 2 + include/Makefile.in | 638 +++ include/mdds/Makefile.am | 28 + include/mdds/Makefile.in | 720 +++ include/mdds/flat_segment_tree.hpp | 885 ++++ include/mdds/flat_segment_tree_def.inl | 889 ++++ include/mdds/flat_segment_tree_itr.hpp | 348 ++ include/mdds/global.hpp | 216 + include/mdds/multi_type_matrix.hpp | 844 ++++ include/mdds/multi_type_matrix_def.inl | 814 +++ include/mdds/multi_type_vector.hpp | 44 + include/mdds/multi_type_vector/Makefile.am | 16 + include/mdds/multi_type_vector/Makefile.in | 707 +++ include/mdds/multi_type_vector/aos/Makefile.am | 8 + include/mdds/multi_type_vector/aos/Makefile.in | 585 +++ include/mdds/multi_type_vector/aos/block_util.hpp | 241 + include/mdds/multi_type_vector/aos/iterator.hpp | 303 ++ include/mdds/multi_type_vector/aos/main.hpp | 1335 +++++ include/mdds/multi_type_vector/aos/main_def.inl | 4837 ++++++++++++++++++ include/mdds/multi_type_vector/block_funcs.hpp | 233 + include/mdds/multi_type_vector/collection.hpp | 280 ++ include/mdds/multi_type_vector/collection_def.inl | 305 ++ include/mdds/multi_type_vector/env.hpp | 35 + include/mdds/multi_type_vector/iterator_node.hpp | 127 + include/mdds/multi_type_vector/macro.hpp | 207 + include/mdds/multi_type_vector/soa/Makefile.am | 8 + include/mdds/multi_type_vector/soa/Makefile.in | 585 +++ include/mdds/multi_type_vector/soa/block_util.hpp | 621 +++ include/mdds/multi_type_vector/soa/iterator.hpp | 414 ++ include/mdds/multi_type_vector/soa/main.hpp | 1449 ++++++ include/mdds/multi_type_vector/soa/main_def.inl | 5238 ++++++++++++++++++++ .../multi_type_vector/standard_element_blocks.hpp | 86 + include/mdds/multi_type_vector/types.hpp | 1011 ++++ include/mdds/multi_type_vector/types_util.hpp | 165 + include/mdds/multi_type_vector/util.hpp | 264 + include/mdds/multi_type_vector_itr.hpp | 36 + include/mdds/multi_type_vector_macro.hpp | 33 + include/mdds/multi_type_vector_types.hpp | 33 + include/mdds/node.hpp | 490 ++ include/mdds/point_quad_tree.hpp | 1576 ++++++ include/mdds/quad_node.hpp | 375 ++ include/mdds/ref_pair.hpp | 80 + include/mdds/rtree.hpp | 830 ++++ include/mdds/rtree_def.inl | 2625 ++++++++++ include/mdds/segment_tree.hpp | 704 +++ include/mdds/segment_tree_def.inl | 644 +++ include/mdds/sorted_string_map.hpp | 125 + include/mdds/sorted_string_map_def.inl | 167 + include/mdds/trie_map.hpp | 713 +++ include/mdds/trie_map_def.inl | 1735 +++++++ include/mdds/trie_map_itr.hpp | 889 ++++ 51 files changed, 35543 insertions(+) create mode 100644 include/Makefile.am create mode 100644 include/Makefile.in create mode 100644 include/mdds/Makefile.am create mode 100644 include/mdds/Makefile.in create mode 100644 include/mdds/flat_segment_tree.hpp create mode 100644 include/mdds/flat_segment_tree_def.inl create mode 100644 include/mdds/flat_segment_tree_itr.hpp create mode 100644 include/mdds/global.hpp create mode 100644 include/mdds/multi_type_matrix.hpp create mode 100644 include/mdds/multi_type_matrix_def.inl create mode 100644 include/mdds/multi_type_vector.hpp create mode 100644 include/mdds/multi_type_vector/Makefile.am create mode 100644 include/mdds/multi_type_vector/Makefile.in create mode 100644 include/mdds/multi_type_vector/aos/Makefile.am create mode 100644 include/mdds/multi_type_vector/aos/Makefile.in create mode 100644 include/mdds/multi_type_vector/aos/block_util.hpp create mode 100644 include/mdds/multi_type_vector/aos/iterator.hpp create mode 100644 include/mdds/multi_type_vector/aos/main.hpp create mode 100644 include/mdds/multi_type_vector/aos/main_def.inl create mode 100644 include/mdds/multi_type_vector/block_funcs.hpp create mode 100644 include/mdds/multi_type_vector/collection.hpp create mode 100644 include/mdds/multi_type_vector/collection_def.inl create mode 100644 include/mdds/multi_type_vector/env.hpp create mode 100644 include/mdds/multi_type_vector/iterator_node.hpp create mode 100644 include/mdds/multi_type_vector/macro.hpp create mode 100644 include/mdds/multi_type_vector/soa/Makefile.am create mode 100644 include/mdds/multi_type_vector/soa/Makefile.in create mode 100644 include/mdds/multi_type_vector/soa/block_util.hpp create mode 100644 include/mdds/multi_type_vector/soa/iterator.hpp create mode 100644 include/mdds/multi_type_vector/soa/main.hpp create mode 100644 include/mdds/multi_type_vector/soa/main_def.inl create mode 100644 include/mdds/multi_type_vector/standard_element_blocks.hpp create mode 100644 include/mdds/multi_type_vector/types.hpp create mode 100644 include/mdds/multi_type_vector/types_util.hpp create mode 100644 include/mdds/multi_type_vector/util.hpp create mode 100644 include/mdds/multi_type_vector_itr.hpp create mode 100644 include/mdds/multi_type_vector_macro.hpp create mode 100644 include/mdds/multi_type_vector_types.hpp create mode 100644 include/mdds/node.hpp create mode 100644 include/mdds/point_quad_tree.hpp create mode 100644 include/mdds/quad_node.hpp create mode 100644 include/mdds/ref_pair.hpp create mode 100644 include/mdds/rtree.hpp create mode 100644 include/mdds/rtree_def.inl create mode 100644 include/mdds/segment_tree.hpp create mode 100644 include/mdds/segment_tree_def.inl create mode 100644 include/mdds/sorted_string_map.hpp create mode 100644 include/mdds/sorted_string_map_def.inl create mode 100644 include/mdds/trie_map.hpp create mode 100644 include/mdds/trie_map_def.inl create mode 100644 include/mdds/trie_map_itr.hpp (limited to 'include') diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..7b38e42 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = mdds + diff --git a/include/Makefile.in b/include/Makefile.in new file mode 100644 index 0000000..6a605b2 --- /dev/null +++ b/include/Makefile.in @@ -0,0 +1,638 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = include +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \ + $(top_srcdir)/m4/m4_ax_valgrind_check.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__extra_recursive_targets = check-valgrind-recursive \ + check-valgrind-memcheck-recursive \ + check-valgrind-helgrind-recursive check-valgrind-drd-recursive \ + check-valgrind-sgcheck-recursive +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +API_VERSION = @API_VERSION@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_NODEBUG = @CPPFLAGS_NODEBUG@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CXX = @CXX@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CXXFLAGS_UNITTESTS = @CXXFLAGS_UNITTESTS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DOXYGEN = @DOXYGEN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +ENABLE_VALGRIND_drd = @ENABLE_VALGRIND_drd@ +ENABLE_VALGRIND_helgrind = @ENABLE_VALGRIND_helgrind@ +ENABLE_VALGRIND_memcheck = @ENABLE_VALGRIND_memcheck@ +ENABLE_VALGRIND_sgcheck = @ENABLE_VALGRIND_sgcheck@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +EXPECT = @EXPECT@ +GDB = @GDB@ +HAVE_CXX17 = @HAVE_CXX17@ +INCDIR = @INCDIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MISCDIR = @MISCDIR@ +MKDIR_P = @MKDIR_P@ +OBJDIR = @OBJDIR@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +QUICKCHECKDIR = @QUICKCHECKDIR@ +RUNTEST_BIN = @RUNTEST_BIN@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINX = @SPHINX@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VALGRIND_ENABLED = @VALGRIND_ENABLED@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +valgrind_enabled_tools = @valgrind_enabled_tools@ +valgrind_tools = @valgrind_tools@ +SUBDIRS = mdds +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign include/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" +check-valgrind-local: +check-valgrind-memcheck-local: +check-valgrind-helgrind-local: +check-valgrind-drd-local: +check-valgrind-sgcheck-local: + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +check-valgrind: check-valgrind-recursive + +check-valgrind-am: check-valgrind-local + +check-valgrind-drd: check-valgrind-drd-recursive + +check-valgrind-drd-am: check-valgrind-drd-local + +check-valgrind-helgrind: check-valgrind-helgrind-recursive + +check-valgrind-helgrind-am: check-valgrind-helgrind-local + +check-valgrind-memcheck: check-valgrind-memcheck-recursive + +check-valgrind-memcheck-am: check-valgrind-memcheck-local + +check-valgrind-sgcheck: check-valgrind-sgcheck-recursive + +check-valgrind-sgcheck-am: check-valgrind-sgcheck-local + +clean: clean-recursive + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am check-valgrind-am check-valgrind-drd-am \ + check-valgrind-drd-local check-valgrind-helgrind-am \ + check-valgrind-helgrind-local check-valgrind-local \ + check-valgrind-memcheck-am check-valgrind-memcheck-local \ + check-valgrind-sgcheck-am check-valgrind-sgcheck-local clean \ + clean-generic cscopelist-am ctags ctags-am distclean \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs installdirs-am maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic pdf \ + pdf-am ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/include/mdds/Makefile.am b/include/mdds/Makefile.am new file mode 100644 index 0000000..7784656 --- /dev/null +++ b/include/mdds/Makefile.am @@ -0,0 +1,28 @@ +SUBDIRS = multi_type_vector + +headersdir = $(includedir)/mdds-@API_VERSION@/mdds + +headers_HEADERS = \ + flat_segment_tree_def.inl \ + flat_segment_tree.hpp \ + flat_segment_tree_itr.hpp \ + global.hpp \ + multi_type_matrix_def.inl \ + multi_type_matrix.hpp \ + multi_type_vector.hpp \ + multi_type_vector_itr.hpp \ + multi_type_vector_macro.hpp \ + multi_type_vector_types.hpp \ + node.hpp \ + point_quad_tree.hpp \ + quad_node.hpp \ + ref_pair.hpp \ + rtree_def.inl \ + rtree.hpp \ + segment_tree_def.inl \ + segment_tree.hpp \ + sorted_string_map_def.inl \ + sorted_string_map.hpp \ + trie_map_def.inl \ + trie_map.hpp \ + trie_map_itr.hpp diff --git a/include/mdds/Makefile.in b/include/mdds/Makefile.in new file mode 100644 index 0000000..4d6d934 --- /dev/null +++ b/include/mdds/Makefile.in @@ -0,0 +1,720 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = include/mdds +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \ + $(top_srcdir)/m4/m4_ax_valgrind_check.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(headers_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(headersdir)" +HEADERS = $(headers_HEADERS) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__extra_recursive_targets = check-valgrind-recursive \ + check-valgrind-memcheck-recursive \ + check-valgrind-helgrind-recursive check-valgrind-drd-recursive \ + check-valgrind-sgcheck-recursive +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +API_VERSION = @API_VERSION@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_NODEBUG = @CPPFLAGS_NODEBUG@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CXX = @CXX@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CXXFLAGS_UNITTESTS = @CXXFLAGS_UNITTESTS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DOXYGEN = @DOXYGEN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +ENABLE_VALGRIND_drd = @ENABLE_VALGRIND_drd@ +ENABLE_VALGRIND_helgrind = @ENABLE_VALGRIND_helgrind@ +ENABLE_VALGRIND_memcheck = @ENABLE_VALGRIND_memcheck@ +ENABLE_VALGRIND_sgcheck = @ENABLE_VALGRIND_sgcheck@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +EXPECT = @EXPECT@ +GDB = @GDB@ +HAVE_CXX17 = @HAVE_CXX17@ +INCDIR = @INCDIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MISCDIR = @MISCDIR@ +MKDIR_P = @MKDIR_P@ +OBJDIR = @OBJDIR@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +QUICKCHECKDIR = @QUICKCHECKDIR@ +RUNTEST_BIN = @RUNTEST_BIN@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINX = @SPHINX@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VALGRIND_ENABLED = @VALGRIND_ENABLED@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +valgrind_enabled_tools = @valgrind_enabled_tools@ +valgrind_tools = @valgrind_tools@ +SUBDIRS = multi_type_vector +headersdir = $(includedir)/mdds-@API_VERSION@/mdds +headers_HEADERS = \ + flat_segment_tree_def.inl \ + flat_segment_tree.hpp \ + flat_segment_tree_itr.hpp \ + global.hpp \ + multi_type_matrix_def.inl \ + multi_type_matrix.hpp \ + multi_type_vector.hpp \ + multi_type_vector_itr.hpp \ + multi_type_vector_macro.hpp \ + multi_type_vector_types.hpp \ + node.hpp \ + point_quad_tree.hpp \ + quad_node.hpp \ + ref_pair.hpp \ + rtree_def.inl \ + rtree.hpp \ + segment_tree_def.inl \ + segment_tree.hpp \ + sorted_string_map_def.inl \ + sorted_string_map.hpp \ + trie_map_def.inl \ + trie_map.hpp \ + trie_map_itr.hpp + +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/mdds/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign include/mdds/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-headersHEADERS: $(headers_HEADERS) + @$(NORMAL_INSTALL) + @list='$(headers_HEADERS)'; test -n "$(headersdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(headersdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(headersdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(headersdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(headersdir)" || exit $$?; \ + done + +uninstall-headersHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(headers_HEADERS)'; test -n "$(headersdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(headersdir)'; $(am__uninstall_files_from_dir) + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" +check-valgrind-local: +check-valgrind-memcheck-local: +check-valgrind-helgrind-local: +check-valgrind-drd-local: +check-valgrind-sgcheck-local: + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile $(HEADERS) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(headersdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +check-valgrind: check-valgrind-recursive + +check-valgrind-am: check-valgrind-local + +check-valgrind-drd: check-valgrind-drd-recursive + +check-valgrind-drd-am: check-valgrind-drd-local + +check-valgrind-helgrind: check-valgrind-helgrind-recursive + +check-valgrind-helgrind-am: check-valgrind-helgrind-local + +check-valgrind-memcheck: check-valgrind-memcheck-recursive + +check-valgrind-memcheck-am: check-valgrind-memcheck-local + +check-valgrind-sgcheck: check-valgrind-sgcheck-recursive + +check-valgrind-sgcheck-am: check-valgrind-sgcheck-local + +clean: clean-recursive + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-headersHEADERS + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-headersHEADERS + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am check-valgrind-am check-valgrind-drd-am \ + check-valgrind-drd-local check-valgrind-helgrind-am \ + check-valgrind-helgrind-local check-valgrind-local \ + check-valgrind-memcheck-am check-valgrind-memcheck-local \ + check-valgrind-sgcheck-am check-valgrind-sgcheck-local clean \ + clean-generic cscopelist-am ctags ctags-am distclean \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-headersHEADERS install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \ + tags-am uninstall uninstall-am uninstall-headersHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/include/mdds/flat_segment_tree.hpp b/include/mdds/flat_segment_tree.hpp new file mode 100644 index 0000000..51c6732 --- /dev/null +++ b/include/mdds/flat_segment_tree.hpp @@ -0,0 +1,885 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2008-2023 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_FLAT_SEGMENT_TREE_HPP +#define INCLUDED_MDDS_FLAT_SEGMENT_TREE_HPP + +#include +#include +#include +#include + +#include "mdds/node.hpp" +#include "mdds/flat_segment_tree_itr.hpp" +#include "mdds/global.hpp" + +#ifdef MDDS_UNIT_TEST +#include +#include +#endif + +namespace mdds { + +template +class flat_segment_tree +{ +public: + typedef Key key_type; + typedef Value value_type; + typedef size_t size_type; + + struct nonleaf_value_type + { + key_type low; /// low range value (inclusive) + key_type high; /// high range value (non-inclusive) + + bool operator==(const nonleaf_value_type& r) const + { + return low == r.low && high == r.high; + } + + nonleaf_value_type() : low{}, high{} + {} + }; + + struct leaf_value_type + { + key_type key; + value_type value; + + bool operator==(const leaf_value_type& r) const + { + return key == r.key && value == r.value; + } + + leaf_value_type() : key{}, value{} + {} + }; + + // Handlers required by the node template class. + struct fill_nonleaf_value_handler; + struct init_handler; + struct dispose_handler; +#ifdef MDDS_UNIT_TEST + struct to_string_handler; +#endif + + typedef __st::node node; + typedef typename node::node_ptr node_ptr; + + typedef __st::nonleaf_node nonleaf_node; + + struct fill_nonleaf_value_handler + { + void operator()( + __st::nonleaf_node& _self, const __st::node_base* left_node, + const __st::node_base* right_node) + { + // Parent node should carry the range of all of its child nodes. + if (left_node) + { + _self.value_nonleaf.low = left_node->is_leaf + ? static_cast(left_node)->value_leaf.key + : static_cast(left_node)->value_nonleaf.low; + } + else + { + // Having a left node is prerequisite. + throw general_error( + "flat_segment_tree::fill_nonleaf_value_handler: Having a left node is prerequisite."); + } + + if (right_node) + { + if (right_node->is_leaf) + { + // When the child nodes are leaf nodes, the upper bound + // must be the value of the node that comes after the + // right leaf node (if such node exists). + const node* p = static_cast(right_node); + if (p->next) + _self.value_nonleaf.high = p->next->value_leaf.key; + else + _self.value_nonleaf.high = p->value_leaf.key; + } + else + { + _self.value_nonleaf.high = static_cast(right_node)->value_nonleaf.high; + } + } + else + { + _self.value_nonleaf.high = left_node->is_leaf + ? static_cast(left_node)->value_leaf.key + : static_cast(left_node)->value_nonleaf.high; + } + } + }; + +#ifdef MDDS_UNIT_TEST + struct to_string_handler + { + std::string operator()(const node& _self) const + { + std::ostringstream os; + os << "(" << _self.value_leaf.key << ") "; + return os.str(); + } + + std::string operator()(const mdds::__st::nonleaf_node& _self) const + { + std::ostringstream os; + os << "(" << _self.value_nonleaf.low << "-" << _self.value_nonleaf.high << ") "; + return os.str(); + } + }; +#endif + + struct init_handler + { + void operator()(node& /*_self*/) + {} + void operator()(__st::nonleaf_node& /*_self*/) + {} + }; + + struct dispose_handler + { + void operator()(node& /*_self*/) + {} + void operator()(__st::nonleaf_node& /*_self*/) + {} + }; + +private: + friend struct ::mdds::fst::detail::forward_itr_handler; + friend struct ::mdds::fst::detail::reverse_itr_handler; + +public: + using const_segment_iterator = mdds::fst::detail::const_segment_iterator; + + class const_iterator : public ::mdds::fst::detail::const_iterator_base< + flat_segment_tree, ::mdds::fst::detail::forward_itr_handler> + { + typedef ::mdds::fst::detail::const_iterator_base< + flat_segment_tree, ::mdds::fst::detail::forward_itr_handler> + base_type; + friend class flat_segment_tree; + + using base_type::get_parent; + using base_type::get_pos; + using base_type::is_end_pos; + + public: + const_iterator() : base_type(nullptr, false) + {} + + /** + * Create a segment iterator that references the same segment the source + * iterator references the start key of. + */ + const_segment_iterator to_segment() const; + + private: + explicit const_iterator(const typename base_type::fst_type* _db, bool _end) : base_type(_db, _end) + {} + + explicit const_iterator(const typename base_type::fst_type* _db, const node* p) : base_type(_db, p) + {} + }; + + class const_reverse_iterator : public ::mdds::fst::detail::const_iterator_base< + flat_segment_tree, ::mdds::fst::detail::reverse_itr_handler> + { + typedef ::mdds::fst::detail::const_iterator_base< + flat_segment_tree, ::mdds::fst::detail::reverse_itr_handler> + base_type; + friend class flat_segment_tree; + + public: + const_reverse_iterator() : base_type(nullptr, false) + {} + + private: + explicit const_reverse_iterator(const typename base_type::fst_type* _db, bool _end) : base_type(_db, _end) + {} + }; + + class const_segment_range_type + { + node_ptr m_left_leaf; + node_ptr m_right_leaf; + + public: + const_segment_range_type(node_ptr left_leaf, node_ptr right_leaf); + + const_segment_iterator begin() const; + const_segment_iterator end() const; + }; + + /** + * Return an iterator that points to the first leaf node that correspondes + * with the start position of the first segment. + * + * @return immutable iterator that points to the first leaf node that + * corresponds with the start position of the first segment. + */ + const_iterator begin() const + { + return const_iterator(this, false); + } + + /** + * Return an iterator that points to the position past the last leaf node + * that corresponds with the end position of the last segment. + * + * @return immutable iterator that points to the position past last leaf + * node that corresponds with the end position of the last + * segment. + */ + const_iterator end() const + { + return const_iterator(this, true); + } + + /** + * Return an iterator that points to the last leaf node that correspondes + * with the end position of the last segment. This iterator moves in the + * reverse direction of a normal iterator. + * + * @return immutable reverse iterator that points to the last leaf node + * that corresponds with the end position of the last segment. + */ + const_reverse_iterator rbegin() const + { + return const_reverse_iterator(this, false); + } + + /** + * Return an iterator that points to the position past the first leaf node + * that corresponds with the start position of the first segment. This + * iterator moves in the reverse direction of a normal iterator. + * + * @return immutable reverse iterator that points to the position past + * first leaf node that corresponds with the start position of the + * first segment. + */ + const_reverse_iterator rend() const + { + return const_reverse_iterator(this, true); + } + + /** + * Return an immutable iterator that points to the first segment stored in + * the tree. It iterates through the segments one segment at a time. + * Each iterator value consists of start, end, + * and value members that correspond with the start and end + * positions of a segment and the value of that segment, respectively. + * + * @return immutable iterator that points to the first segment stored in + * the tree. + */ + const_segment_iterator begin_segment() const; + + /** + * Return an immutable iterator that points to the position past the last + * segment stored in the tree. It iterates through the segments one + * segment at a time. Each iterator value consists of start, + * end, and value members that correspond with + * the start and end positions of a segment and the value of that segment, + * respectively. + * + * @return immutable iterator that points to the position past the last + * segment stored in the tree. + */ + const_segment_iterator end_segment() const; + + /** + * Return a range object that provides a begin iterator and an end sentinel. + */ + const_segment_range_type segment_range() const; + + flat_segment_tree() = delete; + + /** + * Constructor that takes minimum and maximum keys and the value to be + * used for the initial segment. + * + * @param min_val minimum allowed key value for the entire series of + * segments. + * @param max_val maximum allowed key value for the entires series of + * segments. + * @param init_val value to be used for the initial segment. This value + * will also be used for empty segments. + */ + flat_segment_tree(key_type min_val, key_type max_val, value_type init_val); + + /** + * Copy constructor only copies the leaf nodes. + */ + flat_segment_tree(const flat_segment_tree& r); + + /** + * Move constructor. + * + * @warning The source instance will not be usable after the move + * construction. + */ + flat_segment_tree(flat_segment_tree&& other); + + ~flat_segment_tree(); + + /** + * Copy assignment operator. + * + * @param other Source instance to copy from. + * + * @note It only copies the leaf nodes. + */ + flat_segment_tree& operator=(const flat_segment_tree& other); + + /** + * Move assignment operator. + * + * @param other Source instance to move from. + */ + flat_segment_tree& operator=(flat_segment_tree&& other); + + /** + * Swap the content of the tree with another instance. + * + * @param other instance of flat_segment_tree to swap content with. + */ + void swap(flat_segment_tree& other); + + /** + * Remove all stored segments except for the initial segment. The minimum + * and maximum keys and the default value will be retained after the call + * returns. This call will also remove the tree. + */ + void clear(); + + /** + * Insert a new segment into the tree. It searches for the point of + * insertion from the first leaf node. + * + * @param start_key start value of the segment being inserted. The value + * is inclusive. + * @param end_key end value of the segment being inserted. The value is + * not inclusive. + * @param val value associated with this segment. + * + * @return pair of const_iterator corresponding to the start position of + * the inserted segment, and a boolean value indicating whether or + * not the insertion has modified the tree. + */ + std::pair insert_front(key_type start_key, key_type end_key, value_type val) + { + return insert_segment_impl(std::move(start_key), std::move(end_key), std::move(val), true); + } + + /** + * Insert a new segment into the tree. Unlike the insert_front() + * counterpart, this method searches for the point of insertion from the + * last leaf node toward the first. + * + * @param start_key start value of the segment being inserted. The value + * is inclusive. + * @param end_key end value of the segment being inserted. The value is + * not inclusive. + * @param val value associated with this segment. + * + * @return pair of const_iterator corresponding to the start position of + * the inserted segment, and a boolean value indicating whether or + * not the insertion has modified the tree. + */ + std::pair insert_back(key_type start_key, key_type end_key, value_type val) + { + return insert_segment_impl(std::move(start_key), std::move(end_key), std::move(val), false); + } + + /** + * Insert a new segment into the tree at or after specified point of + * insertion. + * + * @param pos specified insertion point + * @param start_key start value of the segment being inserted. The value + * is inclusive. + * @param end_key end value of the segment being inserted. The value is + * not inclusive. + * @param val value associated with this segment. + * + * @return pair of const_iterator corresponding to the start position of + * the inserted segment, and a boolean value indicating whether or + * not the insertion has modified the tree. + */ + std::pair insert( + const const_iterator& pos, key_type start_key, key_type end_key, value_type val); + + /** + * Remove a segment specified by the start and end key values, and shift + * the remaining segments (i.e. those segments that come after the removed + * segment) to left. Note that the start and end positions of the segment + * being removed must be within the base segment span. + * + * @param start_key start position of the segment being removed. + * @param end_key end position of the segment being removed. + */ + void shift_left(key_type start_key, key_type end_key); + + /** + * Shift all segments that occur at or after the specified start position + * to right by the size specified. + * + * @param pos position where the right-shift occurs. + * @param size amount of shift (must be greater than 0) + * @param skip_start_node if true, and the specified position is at an + * existing node position, that node will + * not be shifted. This argument has no + * effect if the position specified does not + * coincide with any of the existing nodes. + */ + void shift_right(key_type pos, key_type size, bool skip_start_node); + + /** + * Perform leaf-node search for a value associated with a key. + * + * @param key key value + * @param value value associated with key specified gets stored upon + * successful search. + * @param start_key pointer to a variable where the start key value of the + * segment that contains the key gets stored upon + * successful search. + * @param end_key pointer to a varaible where the end key value of the + * segment that contains the key gets stored upon + * successful search. + * @return a pair of const_iterator corresponding to the start position of + * the segment containing the key, and a boolean value indicating + * whether or not the search has been successful. + * + */ + std::pair search( + key_type key, value_type& value, key_type* start_key = nullptr, key_type* end_key = nullptr) const; + + /** + * Perform leaf-node search for a value associated with a key. + * + * @param pos position from which the search should start. When the + * position is invalid, it falls back to the normal search. + * @param key key value. + * @param value value associated with key specified gets stored upon + * successful search. + * @param start_key pointer to a variable where the start key value of the + * segment that contains the key gets stored upon + * successful search. + * @param end_key pointer to a varaible where the end key value of the + * segment that contains the key gets stored upon + * successful search. + * @return a pair of const_iterator corresponding to the start position of + * the segment containing the key, and a boolean value indicating + * whether or not the search has been successful. + */ + std::pair search( + const const_iterator& pos, key_type key, value_type& value, key_type* start_key = nullptr, + key_type* end_key = nullptr) const; + + /** + * Perform leaf-node search for a value associated with a key. + * + * @param key Key value to perform search for. + * + * @return Iterator position associated with the start position of the + * segment containing the key, or end iterator position upon search + * failure. + */ + const_iterator search(key_type key) const; + + /** + * Perform leaf-node search for a value associated with a key. + * + * @param pos Position from which the search should start if valid. In case + * of an invalid position, it falls back to a search starting + * with the first position. + * @param key Key value to perform search for. + * + * @return Iterator position associated with the start position of the + * segment containing the key, or end iterator position if the + * search has failed. + */ + const_iterator search(const const_iterator& pos, key_type key) const; + + /** + * Perform tree search for a value associated with a key. This method + * assumes that the tree is valid. Call is_tree_valid() to find out + * whether the tree is valid, and build_tree() to build a new tree in case + * it's not. + * + * @param key key value + * @param value value associated with key specified gets stored upon + * successful search. + * @param start_key pointer to a variable where the start key value of the + * segment that contains the key gets stored upon + * successful search. + * @param end_key pointer to a varaible where the end key value of the + * segment that contains the key gets stored upon + * successful search. + * @return a pair of const_iterator corresponding to the start position of + * the segment containing the key, and a boolean value indicating + * whether or not the search has been successful. + */ + std::pair search_tree( + key_type key, value_type& value, key_type* start_key = nullptr, key_type* end_key = nullptr) const; + + /** + * Perform tree search for a value associated with a key. The tree must be + * valid before performing the search, else the search will fail. + * + * @param key Key value to perform search for. + * + * @return Iterator position associated with the start position of the + * segment containing the key, or end iterator position if the + * search has failed. + */ + const_iterator search_tree(key_type key) const; + + /** + * Build a tree of non-leaf nodes based on the values stored in the leaf + * nodes. The tree must be valid before you can call the search_tree() + * method. + */ + void build_tree(); + + /** + * @return true if the tree is valid, otherwise false. The tree must be + * valid before you can call the search_tree() method. + */ + bool is_tree_valid() const + { + return m_valid_tree; + } + + /** + * Equality between two flat_segment_tree instances is evaluated by + * comparing the keys and the values of the leaf nodes only. Neither the + * non-leaf nodes nor the validity of the tree is evaluated. + */ + bool operator==(const flat_segment_tree& other) const; + + bool operator!=(const flat_segment_tree& other) const + { + return !operator==(other); + } + + key_type min_key() const + { + return m_left_leaf->value_leaf.key; + } + + key_type max_key() const + { + return m_right_leaf->value_leaf.key; + } + + value_type default_value() const + { + return m_init_val; + } + + /** + * Return the number of leaf nodes. + * + * @return number of leaf nodes. + */ + size_type leaf_size() const; + +#ifdef MDDS_UNIT_TEST + const nonleaf_node* get_root_node() const + { + return m_root_node; + } + + void dump_tree() const + { + using ::std::cout; + using ::std::endl; + + if (!m_valid_tree) + assert(!"attempted to dump an invalid tree!"); + + size_t node_count = mdds::__st::tree_dumper::dump(m_root_node); + size_t node_instance_count = node::get_instance_count(); + size_t leaf_count = leaf_size(); + + cout << "tree node count = " << node_count << "; node instance count = " << node_instance_count + << "; leaf node count = " << leaf_count << endl; + + assert(leaf_count == node_instance_count); + } + + void dump_leaf_nodes() const + { + using ::std::cout; + using ::std::endl; + + cout << "------------------------------------------" << endl; + + node_ptr cur_node = m_left_leaf; + long node_id = 0; + while (cur_node) + { + cout << " node " << node_id++ << ": key = " << cur_node->value_leaf.key + << "; value = " << cur_node->value_leaf.value << endl; + cur_node = cur_node->next; + } + cout << endl << " node instance count = " << node::get_instance_count() << endl; + } + + /** + * Verify keys in the leaf nodes. + * + * @param key_values vector containing key values in the left-to-right + * order, including the key value of the rightmost leaf + * node. + */ + bool verify_keys(const ::std::vector& key_values) const + { + { + // Start from the left-most node, and traverse right. + node* cur_node = m_left_leaf.get(); + typename ::std::vector::const_iterator itr = key_values.begin(), itr_end = key_values.end(); + for (; itr != itr_end; ++itr) + { + if (!cur_node) + // Position past the right-mode node. Invalid. + return false; + + if (cur_node->value_leaf.key != *itr) + // Key values differ. + return false; + + cur_node = cur_node->next.get(); + } + + if (cur_node) + // At this point, we expect the current node to be at the position + // past the right-most node, which is nullptr. + return false; + } + + { + // Start from the right-most node, and traverse left. + node* cur_node = m_right_leaf.get(); + typename ::std::vector::const_reverse_iterator itr = key_values.rbegin(), + itr_end = key_values.rend(); + for (; itr != itr_end; ++itr) + { + if (!cur_node) + // Position past the left-mode node. Invalid. + return false; + + if (cur_node->value_leaf.key != *itr) + // Key values differ. + return false; + + cur_node = cur_node->prev.get(); + } + + if (cur_node) + // Likewise, we expect the current position to be past the + // left-most node, in which case the node value is nullptr. + return false; + } + + return true; + } + + /** + * Verify values in the leaf nodes. + * + * @param values vector containing values to verify against, in the + * left-to-right order, not including the value of + * the rightmost leaf node. + */ + bool verify_values(const ::std::vector& values) const + { + node* cur_node = m_left_leaf.get(); + node* end_node = m_right_leaf.get(); + typename ::std::vector::const_iterator itr = values.begin(), itr_end = values.end(); + for (; itr != itr_end; ++itr) + { + if (cur_node == end_node || !cur_node) + return false; + + if (cur_node->value_leaf.value != *itr) + // Key values differ. + return false; + + cur_node = cur_node->next.get(); + } + + if (cur_node != end_node) + // At this point, we expect the current node to be at the end of + // range. + return false; + + return true; + } +#endif + +private: + const_iterator search_by_key_impl(const node* start_pos, key_type key) const; + + const node* search_tree_for_leaf_node(key_type key) const; + + void append_new_segment(key_type start_key) + { + if (m_right_leaf->prev->value_leaf.key == start_key) + { + m_right_leaf->prev->value_leaf.value = m_init_val; + return; + } + +#ifdef MDDS_UNIT_TEST + // The start position must come after the position of the last node + // before the right-most node. + assert(m_right_leaf->prev->value_leaf.key < start_key); +#endif + + if (m_right_leaf->prev->value_leaf.value == m_init_val) + // The existing segment has the same value. No need to insert a + // new segment. + return; + + node_ptr new_node(new node); + new_node->value_leaf.key = start_key; + new_node->value_leaf.value = m_init_val; + new_node->prev = m_right_leaf->prev; + new_node->next = m_right_leaf; + m_right_leaf->prev->next = new_node; + m_right_leaf->prev = new_node; + m_valid_tree = false; + } + + ::std::pair insert_segment_impl( + key_type start_key, key_type end_key, value_type val, bool forward); + + ::std::pair insert_to_pos( + node_ptr start_pos, key_type start_key, key_type end_key, value_type val); + + ::std::pair search_impl( + const node* pos, key_type key, value_type& value, key_type* start_key, key_type* end_key) const; + + const node* get_insertion_pos_leaf_reverse(const key_type& key, const node* start_pos) const; + + /** + * Find a node with the largest value whose key equals or less than a + * specified key, starting with a specific node. + * + * @pre The caller must ensure that the key equals or greater than the min + * key. + * + * @note If the key exceeds the max key value, it will return + * nullptr. + */ + const node* get_insertion_pos_leaf(const key_type& key, const node* start_pos) const; + + static void shift_leaf_key_left(node_ptr& begin_node, node_ptr& end_node, key_type shift_value) + { + node* cur_node_p = begin_node.get(); + node* end_node_p = end_node.get(); + while (cur_node_p != end_node_p) + { + cur_node_p->value_leaf.key -= shift_value; + cur_node_p = cur_node_p->next.get(); + } + } + + static void shift_leaf_key_right(node_ptr& cur_node, node_ptr& end_node, key_type shift_value) + { + key_type end_node_key = end_node->value_leaf.key; + while (cur_node.get() != end_node.get()) + { + cur_node->value_leaf.key += shift_value; + if (cur_node->value_leaf.key < end_node_key) + { + // The node is still in-bound. Keep shifting. + cur_node = cur_node->next; + continue; + } + + // This node has been pushed outside the end node position. + // Remove all nodes that follows, and connect the previous node + // with the end node. + + node_ptr last_node = cur_node->prev; + while (cur_node.get() != end_node.get()) + { + node_ptr next_node = cur_node->next; + disconnect_all_nodes(cur_node.get()); + cur_node = next_node; + } + last_node->next = end_node; + end_node->prev = last_node; + return; + } + } + + void destroy(); + + /** + * Check and optionally adjust the start and end key values if one of them + * is out-of-bound. + * + * @return true if the start and end key values are valid, either with or + * without adjustments, otherwise false. + */ + bool adjust_segment_range(key_type& start_key, key_type& end_key) const; + +private: + std::vector m_nonleaf_node_pool; + + const nonleaf_node* m_root_node; + node_ptr m_left_leaf; + node_ptr m_right_leaf; + value_type m_init_val; + bool m_valid_tree; +}; + +template +void swap(flat_segment_tree& left, flat_segment_tree& right) +{ + left.swap(right); +} + +} // namespace mdds + +#include "flat_segment_tree_def.inl" + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/flat_segment_tree_def.inl b/include/mdds/flat_segment_tree_def.inl new file mode 100644 index 0000000..5398b06 --- /dev/null +++ b/include/mdds/flat_segment_tree_def.inl @@ -0,0 +1,889 @@ +/************************************************************************* + * + * Copyright (c) 2010-2023 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +namespace mdds { + +template +typename flat_segment_tree::const_segment_iterator flat_segment_tree< + Key, Value>::const_iterator::to_segment() const +{ + const node* pos = get_pos(); + const auto* parent = get_parent(); + if (!parent || is_end_pos() || !pos || !pos->next) + return parent->end_segment(); + + return const_segment_iterator(pos, pos->next.get()); +} + +template +flat_segment_tree::const_segment_range_type::const_segment_range_type( + node_ptr left_leaf, node_ptr right_leaf) + : m_left_leaf(left_leaf), m_right_leaf(right_leaf) +{} + +template +typename flat_segment_tree::const_segment_iterator flat_segment_tree< + Key, Value>::const_segment_range_type::begin() const +{ + return const_segment_iterator(m_left_leaf.get(), m_left_leaf->next.get()); +} + +template +typename flat_segment_tree::const_segment_iterator flat_segment_tree< + Key, Value>::const_segment_range_type::end() const +{ + return const_segment_iterator(m_right_leaf.get(), nullptr); +} + +template +typename flat_segment_tree::const_segment_iterator flat_segment_tree::begin_segment() const +{ + return const_segment_iterator(m_left_leaf.get(), m_left_leaf->next.get()); +} + +template +typename flat_segment_tree::const_segment_iterator flat_segment_tree::end_segment() const +{ + return const_segment_iterator(m_right_leaf.get(), nullptr); +} + +template +typename flat_segment_tree::const_segment_range_type flat_segment_tree::segment_range() const +{ + return const_segment_range_type(m_left_leaf, m_right_leaf); +} + +template +flat_segment_tree::flat_segment_tree(key_type min_val, key_type max_val, value_type init_val) + : m_root_node(nullptr), m_left_leaf(new node), m_right_leaf(new node), m_init_val(std::move(init_val)), + m_valid_tree(false) +{ + // we need to create two end nodes during initialization. + m_left_leaf->value_leaf.key = std::move(min_val); + m_left_leaf->value_leaf.value = m_init_val; // copy + m_left_leaf->next = m_right_leaf; + + m_right_leaf->value_leaf.key = std::move(max_val); + m_right_leaf->prev = m_left_leaf; + + // We don't ever use the value of the right leaf node, but we need the + // value to be always the same, to make it easier to check for + // equality. + m_right_leaf->value_leaf.value = value_type{}; +} + +template +flat_segment_tree::flat_segment_tree(const flat_segment_tree& r) + : m_root_node(nullptr), m_left_leaf(new node(*r.m_left_leaf)), m_right_leaf(), m_init_val(r.m_init_val), + m_valid_tree(false) // tree is invalid because we only copy the leaf nodes. +{ + // Copy all the leaf nodes from the original instance. + node* src_node = r.m_left_leaf.get(); + node_ptr dest_node = m_left_leaf; + while (true) + { + dest_node->next.reset(new node(*src_node->next)); + + // Move on to the next source node. + src_node = src_node->next.get(); + + // Move on to the next destination node, and have the next node point + // back to the previous node. + node_ptr old_node = dest_node; + dest_node = dest_node->next; + dest_node->prev = old_node; + + if (src_node == r.m_right_leaf.get()) + { + // Reached the right most leaf node. We can stop here. + m_right_leaf = dest_node; + break; + } + } +} + +template +flat_segment_tree::flat_segment_tree(flat_segment_tree&& other) + : m_nonleaf_node_pool(std::move(other.m_nonleaf_node_pool)), m_root_node(other.m_root_node), + m_left_leaf(other.m_left_leaf), m_right_leaf(other.m_right_leaf), m_init_val(other.m_init_val), + m_valid_tree(other.m_valid_tree) +{ + // NB: boost::intrusive_ptr doesn't have move constructor + other.m_left_leaf.reset(); + other.m_right_leaf.reset(); + other.m_root_node = nullptr; + other.m_valid_tree = false; +} + +template +flat_segment_tree::~flat_segment_tree() +{ + destroy(); +} + +template +flat_segment_tree& flat_segment_tree::operator=(const flat_segment_tree& other) +{ + flat_segment_tree copy(other); + swap(copy); + return *this; +} + +template +flat_segment_tree& flat_segment_tree::operator=(flat_segment_tree&& other) +{ + flat_segment_tree moved(std::move(other)); + swap(moved); + return *this; +} + +template +void flat_segment_tree::swap(flat_segment_tree& other) +{ + m_nonleaf_node_pool.swap(other.m_nonleaf_node_pool); + std::swap(m_root_node, other.m_root_node); + std::swap(m_left_leaf, other.m_left_leaf); + std::swap(m_right_leaf, other.m_right_leaf); + std::swap(m_init_val, other.m_init_val); + std::swap(m_valid_tree, other.m_valid_tree); +} + +template +void flat_segment_tree::clear() +{ + // the border nodes should not be destroyed--add a ref to keep them alive + node_ptr left(m_left_leaf); + node_ptr right(m_right_leaf); + + // destroy the tree + destroy(); + + // and construct the default tree + __st::link_nodes(m_left_leaf, m_right_leaf); + m_left_leaf->value_leaf.value = m_init_val; + m_valid_tree = false; +} + +template +::std::pair::const_iterator, bool> flat_segment_tree< + Key, Value>::insert_segment_impl(key_type start_key, key_type end_key, value_type val, bool forward) +{ + typedef std::pair::const_iterator, bool> ret_type; + + if (!adjust_segment_range(start_key, end_key)) + return ret_type(const_iterator(this, true), false); + + // Find the node with value that either equals or is greater than the + // start value. + + node_ptr start_pos; + if (forward) + { + const node* p = get_insertion_pos_leaf(start_key, m_left_leaf.get()); + start_pos.reset(const_cast(p)); + } + else + { + const node* p = get_insertion_pos_leaf_reverse(start_key, m_right_leaf.get()); + if (p) + start_pos = p->next; + else + start_pos = m_left_leaf; + } + if (!start_pos) + { + // Insertion position not found. Bail out. + assert(!"Insertion position not found. Bail out"); + return ret_type(const_iterator(this, true), false); + } + + return insert_to_pos(start_pos, std::move(start_key), std::move(end_key), std::move(val)); +} + +template +::std::pair::const_iterator, bool> flat_segment_tree::insert_to_pos( + node_ptr start_pos, key_type start_key, key_type end_key, value_type val) +{ + node_ptr end_pos; + { + const node* p = get_insertion_pos_leaf(end_key, start_pos.get()); + end_pos.reset(const_cast(p)); + } + if (!end_pos) + end_pos = m_right_leaf; + + node_ptr new_start_node; + value_type old_value; + + // Set the start node. + + bool changed = false; + + if (start_pos->value_leaf.key == start_key) + { + // Re-use the existing node, but save the old value for later. + + if (start_pos->prev && start_pos->prev->value_leaf.value == val) + { + // Extend the existing segment. + old_value = start_pos->value_leaf.value; + new_start_node = start_pos->prev; + } + else + { + // Update the value of the existing node. + old_value = start_pos->value_leaf.value; + start_pos->value_leaf.value = val; + new_start_node = start_pos; + + changed = (old_value != val); + } + } + else if (start_pos->prev->value_leaf.value == val) + { + // Extend the existing segment. + old_value = start_pos->prev->value_leaf.value; + new_start_node = start_pos->prev; + } + else + { + // Insert a new node before the insertion position node. + node_ptr new_node(new node); + new_node->value_leaf.key = std::move(start_key); + new_node->value_leaf.value = val; + new_start_node = new_node; + + node_ptr left_node = start_pos->prev; + old_value = left_node->value_leaf.value; + + // Link to the left node. + __st::link_nodes(left_node, new_node); + + // Link to the right node. + __st::link_nodes(new_node, start_pos); + changed = true; + } + + node_ptr cur_node = new_start_node->next; + while (cur_node != end_pos) + { + // Disconnect the link between the current node and the previous node. + cur_node->prev->next.reset(); + cur_node->prev.reset(); + old_value = cur_node->value_leaf.value; + + cur_node = cur_node->next; + changed = true; + } + + // Set the end node. + + if (end_pos->value_leaf.key == end_key) + { + // The new segment ends exactly at the end node position. + + if (end_pos->next && end_pos->value_leaf.value == val) + { + // Remove this node, and connect the new start node with the + // node that comes after this node. + new_start_node->next = end_pos->next; + if (end_pos->next) + end_pos->next->prev = new_start_node; + disconnect_all_nodes(end_pos.get()); + changed = true; + } + else if (new_start_node->next != end_pos) + { + // Just link the new segment to this node. + new_start_node->next = end_pos; + end_pos->prev = new_start_node; + changed = true; + } + } + else if (old_value == val) + { + if (new_start_node->next != end_pos) + { + __st::link_nodes(new_start_node, end_pos); + changed = true; + } + } + else + { + // Insert a new node before the insertion position node. + node_ptr new_node(new node); + new_node->value_leaf.key = std::move(end_key); + new_node->value_leaf.value = old_value; + + // Link to the left node. + __st::link_nodes(new_start_node, new_node); + + // Link to the right node. + __st::link_nodes(new_node, end_pos); + changed = true; + } + + if (changed) + m_valid_tree = false; + + return ::std::pair(const_iterator(this, new_start_node.get()), changed); +} + +template +::std::pair::const_iterator, bool> flat_segment_tree::insert( + const const_iterator& pos, key_type start_key, key_type end_key, value_type val) +{ + const node* p = pos.get_pos(); + if (!p || this != pos.get_parent()) + { + // Switch to normal insert. + return insert_front(start_key, end_key, val); + } + + assert(p->is_leaf); + + if (start_key < p->value_leaf.key) + { + // Specified position is already past the start key position. Not good. + return insert_front(start_key, end_key, val); + } + + if (!adjust_segment_range(start_key, end_key)) + { + typedef std::pair::const_iterator, bool> ret_type; + return ret_type(const_iterator(this, true), false); + } + + p = get_insertion_pos_leaf(start_key, p); + node_ptr start_pos(const_cast(p)); + return insert_to_pos(start_pos, start_key, end_key, val); +} + +template +void flat_segment_tree::shift_left(key_type start_key, key_type end_key) +{ + if (start_key >= end_key) + return; + + key_type left_leaf_key = m_left_leaf->value_leaf.key; + key_type right_leaf_key = m_right_leaf->value_leaf.key; + if (start_key < left_leaf_key || end_key < left_leaf_key) + // invalid key value + return; + + if (start_key > right_leaf_key || end_key > right_leaf_key) + // invalid key value. + return; + + node_ptr node_pos; + if (left_leaf_key == start_key) + node_pos = m_left_leaf; + else + { + // Get the first node with a key value equal to or greater than the + // start key value. But we want to skip the leftmost node. + const node* p = get_insertion_pos_leaf(start_key, m_left_leaf->next.get()); + node_pos.reset(const_cast(p)); + } + + if (!node_pos) + return; + + key_type segment_size = end_key - start_key; + + if (node_pos == m_right_leaf) + { + // The segment being removed begins after the last node before the + // right-most node. + + if (right_leaf_key <= end_key) + { + // The end position equals or is past the right-most node. + append_new_segment(start_key); + } + else + { + // The end position stops before the right-most node. Simply + // append the blank segment to the end. + append_new_segment(right_leaf_key - segment_size); + } + return; + } + + if (end_key < node_pos->value_leaf.key) + { + // The removed segment does not overlap with any nodes. Simply + // shift the key values of those nodes that come after the removed + // segment. + shift_leaf_key_left(node_pos, m_right_leaf, segment_size); + append_new_segment(right_leaf_key - segment_size); + m_valid_tree = false; + return; + } + + // Move the first node to the starting position, and from there search + // for the first node whose key value is greater than the end value. + node_pos->value_leaf.key = start_key; + node_ptr start_pos = node_pos; + node_pos = node_pos->next; + value_type last_seg_value = start_pos->value_leaf.value; + while (node_pos.get() != m_right_leaf.get() && node_pos->value_leaf.key <= end_key) + { + last_seg_value = node_pos->value_leaf.value; + node_ptr next = node_pos->next; + disconnect_all_nodes(node_pos.get()); + node_pos = next; + } + + start_pos->value_leaf.value = last_seg_value; + start_pos->next = node_pos; + node_pos->prev = start_pos; + if (start_pos->prev && start_pos->prev->value_leaf.value == start_pos->value_leaf.value) + { + // Removing a segment resulted in two consecutive segments with + // identical value. Combine them by removing the 2nd redundant + // node. + start_pos->prev->next = start_pos->next; + start_pos->next->prev = start_pos->prev; + disconnect_all_nodes(start_pos.get()); + } + + shift_leaf_key_left(node_pos, m_right_leaf, segment_size); + m_valid_tree = false; + + // Insert at the end a new segment with the initial base value, for + // the length of the removed segment. + append_new_segment(right_leaf_key - segment_size); +} + +template +void flat_segment_tree::shift_right(key_type pos, key_type size, bool skip_start_node) +{ + if (size <= 0) + return; + + if (pos < m_left_leaf->value_leaf.key || m_right_leaf->value_leaf.key <= pos) + // specified position is out-of-bound + return; + + if (m_left_leaf->value_leaf.key == pos) + { + // Position is at the leftmost node. Shift all the other nodes, + // and insert a new node at (pos + size) position. + node_ptr cur_node = m_left_leaf->next; + shift_leaf_key_right(cur_node, m_right_leaf, size); + + if (m_left_leaf->value_leaf.value != m_init_val && !skip_start_node) + { + if (size < m_right_leaf->value_leaf.key - m_left_leaf->value_leaf.key) + { + // The leftmost leaf node has a non-initial value. We need to + // insert a new node to carry that value after the shift. + node_ptr new_node(new node); + new_node->value_leaf.key = pos + size; + new_node->value_leaf.value = m_left_leaf->value_leaf.value; + m_left_leaf->value_leaf.value = m_init_val; + new_node->prev = m_left_leaf; + new_node->next = m_left_leaf->next; + m_left_leaf->next->prev = new_node; + m_left_leaf->next = new_node; + } + else + { + // We shifted out the whole range, so there would be no new + // node inserted. Just set default value. + m_left_leaf->value_leaf.value = m_init_val; + } + } + + m_valid_tree = false; + return; + } + + // Get the first node with a key value equal to or greater than the + // start key value. But we want to skip the leftmost node. + const node* p = get_insertion_pos_leaf(pos, m_left_leaf->next.get()); + node_ptr cur_node(const_cast(p)); + + // If the point of insertion is at an existing node position, don't + // shift that node but start with the one after it if that's + // requested. + if (skip_start_node && cur_node && cur_node->value_leaf.key == pos) + cur_node = cur_node->next; + + if (!cur_node) + return; + + shift_leaf_key_right(cur_node, m_right_leaf, size); + m_valid_tree = false; +} + +template +::std::pair::const_iterator, bool> flat_segment_tree::search_impl( + const node* pos, key_type key, value_type& value, key_type* start_key, key_type* end_key) const +{ + typedef ::std::pair ret_type; + + if (pos->value_leaf.key == key) + { + value = pos->value_leaf.value; + if (start_key) + *start_key = pos->value_leaf.key; + if (end_key && pos->next) + *end_key = pos->next->value_leaf.key; + return ret_type(const_iterator(this, pos), true); + } + else if (pos->prev && pos->prev->value_leaf.key < key) + { + value = pos->prev->value_leaf.value; + if (start_key) + *start_key = pos->prev->value_leaf.key; + if (end_key) + *end_key = pos->value_leaf.key; + return ret_type(const_iterator(this, pos->prev.get()), true); + } + + return ret_type(const_iterator(this, true), false); +} + +template +::std::pair::const_iterator, bool> flat_segment_tree::search( + key_type key, value_type& value, key_type* start_key, key_type* end_key) const +{ + typedef ::std::pair ret_type; + + if (key < m_left_leaf->value_leaf.key || m_right_leaf->value_leaf.key <= key) + // key value is out-of-bound. + return ret_type(const_iterator(this, true), false); + + const node* pos = get_insertion_pos_leaf(key, m_left_leaf.get()); + return search_impl(pos, key, value, start_key, end_key); +} + +template +::std::pair::const_iterator, bool> flat_segment_tree::search( + const const_iterator& pos, key_type key, value_type& value, key_type* start_key, key_type* end_key) const +{ + typedef ::std::pair ret_type; + + if (key < m_left_leaf->value_leaf.key || m_right_leaf->value_leaf.key <= key) + // key value is out-of-bound. + return ret_type(const_iterator(this, true), false); + + const node* p = pos.get_pos(); + if (!p || this != pos.get_parent()) + { + // Switch to normal search. + return search(key, value, start_key, end_key); + } + + assert(p->is_leaf); + + if (key < p->value_leaf.key) + { + // Specified position is already past the start key position. Fall + // back to normal search. + return search(key, value, start_key, end_key); + } + + p = get_insertion_pos_leaf(key, p); + return search_impl(p, key, value, start_key, end_key); +} + +template +typename flat_segment_tree::const_iterator flat_segment_tree::search(key_type key) const +{ + return search_by_key_impl(m_left_leaf.get(), key); +} + +template +typename flat_segment_tree::const_iterator flat_segment_tree::search( + const const_iterator& pos, key_type key) const +{ + return search_by_key_impl(pos.get_pos(), key); +} + +template +typename flat_segment_tree::const_iterator flat_segment_tree::search_by_key_impl( + const node* start_pos, key_type key) const +{ + if (key < m_left_leaf->value_leaf.key || m_right_leaf->value_leaf.key <= key) + // key value is out-of-bound. + return const_iterator(this, true); + + const node* pos = get_insertion_pos_leaf(key, start_pos); + if (!pos) + return const_iterator(this, true); + + if (pos->value_leaf.key == key) + return const_iterator(this, pos); + else if (pos->prev && pos->prev->value_leaf.key < key) + return const_iterator(this, pos->prev.get()); + + return const_iterator(this, true); +} + +template +std::pair::const_iterator, bool> flat_segment_tree::search_tree( + key_type key, value_type& value, key_type* start_key, key_type* end_key) const +{ + using ret_type = std::pair; + + const node* dest_node = search_tree_for_leaf_node(key); + if (!dest_node) + return ret_type(const_iterator(this, true), false); + + value = dest_node->value_leaf.value; + if (start_key) + *start_key = dest_node->value_leaf.key; + + if (end_key) + { + assert(dest_node->next); + if (dest_node->next) + *end_key = dest_node->next->value_leaf.key; + else + // This should never happen, but just in case.... + *end_key = m_right_leaf->value_leaf.key; + } + + return ret_type(const_iterator(this, dest_node), true); +} + +template +typename flat_segment_tree::const_iterator flat_segment_tree::search_tree(key_type key) const +{ + const node* dest_node = search_tree_for_leaf_node(key); + if (!dest_node) + return const_iterator(this, true); + + return const_iterator(this, dest_node); +} + +template +const typename flat_segment_tree::node* flat_segment_tree::search_tree_for_leaf_node( + key_type key) const +{ + if (!m_root_node || !m_valid_tree) + { + // either tree has not been built, or is in an invalid state. + return nullptr; + } + + if (key < m_left_leaf->value_leaf.key || m_right_leaf->value_leaf.key <= key) + { + // key value is out-of-bound. + return nullptr; + } + + // Descend down the tree through the last non-leaf layer. + + const nonleaf_node* cur_node = m_root_node; + while (true) + { + if (cur_node->left) + { + if (cur_node->left->is_leaf) + break; + + const nonleaf_node* left_nonleaf = static_cast(cur_node->left); + const nonleaf_value_type& v = left_nonleaf->value_nonleaf; + if (v.low <= key && key < v.high) + { + // Descend one level through the left child node. + cur_node = left_nonleaf; + continue; + } + } + else + { + // left child node can't be missing ! + return nullptr; + } + + if (cur_node->right) + { + assert(!cur_node->right->is_leaf); + const nonleaf_node* right_nonleaf = static_cast(cur_node->right); + const nonleaf_value_type& v = right_nonleaf->value_nonleaf; + if (v.low <= key && key < v.high) + { + // Descend one level through the right child node. + cur_node = right_nonleaf; + continue; + } + } + return nullptr; + } + + // Current node must be a non-leaf whose child nodes are leaf nodes. + assert(cur_node->left->is_leaf && cur_node->right->is_leaf); + + const node* dest_node = nullptr; + const node* leaf_left = static_cast(cur_node->left); + const node* leaf_right = static_cast(cur_node->right); + key_type key1 = leaf_left->value_leaf.key; + key_type key2 = leaf_right->value_leaf.key; + + if (key1 <= key && key < key2) + { + dest_node = leaf_left; + } + else if (key2 <= key && key < cur_node->value_nonleaf.high) + { + dest_node = leaf_right; + } + + if (!dest_node) + return nullptr; + + return dest_node; +} + +template +void flat_segment_tree::build_tree() +{ + if (!m_left_leaf) + return; + + m_nonleaf_node_pool.clear(); + + // Count the number of leaf nodes. + size_t leaf_count = leaf_size(); + + // Determine the total number of non-leaf nodes needed to build the whole tree. + size_t nonleaf_count = __st::count_needed_nonleaf_nodes(leaf_count); + + m_nonleaf_node_pool.resize(nonleaf_count); + mdds::__st::tree_builder builder(m_nonleaf_node_pool); + m_root_node = builder.build(m_left_leaf); + m_valid_tree = true; +} + +template +typename flat_segment_tree::size_type flat_segment_tree::leaf_size() const +{ + return __st::count_leaf_nodes(m_left_leaf.get(), m_right_leaf.get()); +} + +template +bool flat_segment_tree::operator==(const flat_segment_tree& other) const +{ + const node* n1 = m_left_leaf.get(); + const node* n2 = other.m_left_leaf.get(); + + if ((!n1 && n2) || (n1 && !n2)) + // Either one of them is nullptr; + return false; + + while (n1) + { + if (!n2) + return false; + + if (!n1->equals(*n2)) + return false; + + n1 = n1->next.get(); + n2 = n2->next.get(); + } + + if (n2) + // n1 is nullptr, but n2 is not. + return false; + + // All leaf nodes are equal. + return true; +} + +template +const typename flat_segment_tree::node* flat_segment_tree::get_insertion_pos_leaf_reverse( + const key_type& key, const node* start_pos) const +{ + const node* cur_node = start_pos; + while (cur_node) + { + if (cur_node->value_leaf.key < key) + { + // Found the insertion position. + return cur_node; + } + cur_node = cur_node->prev.get(); + } + return nullptr; +} + +template +const typename flat_segment_tree::node* flat_segment_tree::get_insertion_pos_leaf( + const key_type& key, const node* start_pos) const +{ + assert(m_left_leaf->value_leaf.key <= key); + + const node* cur_node = start_pos; + while (cur_node) + { + if (key <= cur_node->value_leaf.key) + { + // Found the insertion position. + return cur_node; + } + cur_node = cur_node->next.get(); + } + return nullptr; +} + +template +void flat_segment_tree::destroy() +{ + disconnect_leaf_nodes(m_left_leaf.get(), m_right_leaf.get()); + m_nonleaf_node_pool.clear(); + m_root_node = nullptr; +} + +template +bool flat_segment_tree::adjust_segment_range(key_type& start_key, key_type& end_key) const +{ + if (end_key <= start_key) + // Invalid order of segment range. + return false; + + if (end_key < m_left_leaf->value_leaf.key || m_right_leaf->value_leaf.key <= start_key) + // The new segment does not overlap the current interval. + return false; + + if (start_key < m_left_leaf->value_leaf.key) + // The start value should not be smaller than the current minimum. + start_key = m_left_leaf->value_leaf.key; + + if (m_right_leaf->value_leaf.key < end_key) + // The end value should not be larger than the current maximum. + end_key = m_right_leaf->value_leaf.key; + + return true; +} + +} // namespace mdds diff --git a/include/mdds/flat_segment_tree_itr.hpp b/include/mdds/flat_segment_tree_itr.hpp new file mode 100644 index 0000000..77b077d --- /dev/null +++ b/include/mdds/flat_segment_tree_itr.hpp @@ -0,0 +1,348 @@ +/************************************************************************* + * + * Copyright (c) 2010-2023 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_FLAT_SEGMENT_TREE_ITR_HPP +#define INCLUDED_MDDS_FLAT_SEGMENT_TREE_ITR_HPP + +namespace mdds { namespace fst { namespace detail { + +/** + * Handler for forward iterator + */ +template +struct forward_itr_handler +{ + using fst_type = FstType; + + static const typename fst_type::node* init_pos(const fst_type* _db, bool _end) + { + return _end ? _db->m_right_leaf.get() : _db->m_left_leaf.get(); + } + + static void inc(const fst_type* _db, const typename fst_type::node*& p, bool& end) + { + if (p == _db->m_right_leaf.get()) + end = true; + else + p = p->next.get(); + } + + static void dec(const typename fst_type::node*& p, bool& end) + { + if (end) + end = false; + else + p = p->prev.get(); + } +}; + +/** + * Handler for reverse iterator + */ +template +struct reverse_itr_handler +{ + using fst_type = FstType; + + static const typename fst_type::node* init_pos(const fst_type* _db, bool _end) + { + return _end ? _db->m_left_leaf.get() : _db->m_right_leaf.get(); + } + + static void inc(const fst_type* _db, const typename fst_type::node*& p, bool& end) + { + if (p == _db->m_left_leaf.get()) + end = true; + else + p = p->prev.get(); + } + + static void dec(const typename fst_type::node*& p, bool& end) + { + if (end) + end = false; + else + p = p->next.get(); + } +}; + +template +class const_iterator_base +{ + typedef Hdl handler_type; + +public: + typedef FstType fst_type; + + // iterator traits + typedef ::std::pair value_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef ptrdiff_t difference_type; + typedef ::std::bidirectional_iterator_tag iterator_category; + + explicit const_iterator_base(const fst_type* _db, bool _end) : m_db(_db), m_pos(nullptr), m_end_pos(_end) + { + if (!_db) + return; + + m_pos = handler_type::init_pos(_db, _end); + } + + explicit const_iterator_base(const fst_type* _db, const typename fst_type::node* pos) + : m_db(_db), m_pos(pos), m_end_pos(false) + {} + + const_iterator_base(const const_iterator_base& r) : m_db(r.m_db), m_pos(r.m_pos), m_end_pos(r.m_end_pos) + {} + + const_iterator_base& operator=(const const_iterator_base& r) + { + m_db = r.m_db; + m_pos = r.m_pos; + m_end_pos = r.m_end_pos; + return *this; + } + + const_iterator_base& operator++() + { + assert(m_pos); + handler_type::inc(m_db, m_pos, m_end_pos); + return *this; + } + + const_iterator_base& operator--() + { + assert(m_pos); + handler_type::dec(m_pos, m_end_pos); + return *this; + } + + bool operator==(const const_iterator_base& r) const + { + if (m_db != r.m_db) + return false; + + return (m_pos == r.m_pos) && (m_end_pos == r.m_end_pos); + } + + bool operator!=(const const_iterator_base& r) const + { + return !operator==(r); + } + + const value_type& operator*() + { + return get_current_node_pair(); + } + + const value_type* operator->() + { + return &get_current_node_pair(); + } + +protected: + const typename fst_type::node* get_pos() const + { + return m_pos; + } + + const fst_type* get_parent() const + { + return m_db; + } + + bool is_end_pos() const + { + return m_end_pos; + } + +private: + const value_type& get_current_node_pair() + { + m_current_pair = value_type(m_pos->value_leaf.key, m_pos->value_leaf.value); + return m_current_pair; + } + + const fst_type* m_db; + const typename fst_type::node* m_pos; + value_type m_current_pair; + bool m_end_pos; +}; + +template +class const_segment_iterator +{ + typedef FstType fst_type; + friend fst_type; + + const_segment_iterator(const typename fst_type::node* start, const typename fst_type::node* end) + : m_start(start), m_end(end) + { + update_node(); + } + +public: + struct value_type + { + typename fst_type::key_type start; + typename fst_type::key_type end; + typename fst_type::value_type value; + + value_type() : start(), end(), value() + {} + + value_type( + typename fst_type::key_type _start, typename fst_type::key_type _end, typename fst_type::value_type _value) + : start(std::move(_start)), end(std::move(_end)), value(std::move(_value)) + {} + + bool operator==(const value_type& other) const + { + return start == other.start && end == other.end && value == other.value; + } + + bool operator!=(const value_type& other) const + { + return !operator==(other); + } + }; + + const_segment_iterator() : m_start(nullptr), m_end(nullptr) + {} + const_segment_iterator(const const_segment_iterator& other) : m_start(other.m_start), m_end(other.m_end) + { + if (m_start) + update_node(); + } + const_segment_iterator(const_segment_iterator&& other) + : m_start(std::move(other.m_start)), m_end(std::move(other.m_end)) + { + if (m_start) + update_node(); + } + + ~const_segment_iterator() + {} + + bool operator==(const const_segment_iterator& other) const + { + return m_start == other.m_start && m_end == other.m_end; + } + + bool operator!=(const const_segment_iterator& other) const + { + return !operator==(other); + } + + const_segment_iterator& operator=(const const_segment_iterator& other) + { + m_start = other.m_start; + m_end = other.m_end; + if (m_start) + update_node(); + return *this; + } + + const_segment_iterator& operator=(const_segment_iterator&& other) + { + m_start = std::move(other.m_start); + m_end = std::move(other.m_end); + if (m_start) + update_node(); + return *this; + } + + const value_type& operator*() + { + return m_node; + } + + const value_type* operator->() + { + return &m_node; + } + + const_segment_iterator& operator++() + { + assert(m_start); + m_start = m_start->next.get(); + m_end = m_start->next.get(); + update_node(); + return *this; + } + + const_segment_iterator operator++(int) + { + assert(m_start); + const_segment_iterator ret = *this; + m_start = m_start->next.get(); + m_end = m_start->next.get(); + update_node(); + return ret; + } + + const_segment_iterator& operator--() + { + assert(m_start); + m_start = m_start->prev.get(); + m_end = m_start->next.get(); + update_node(); + return *this; + } + + const_segment_iterator operator--(int) + { + assert(m_start); + const_segment_iterator ret = *this; + m_start = m_start->prev.get(); + m_end = m_start->next.get(); + update_node(); + return ret; + } + +private: + void update_node() + { + if (!m_end) + // The iterator is at its end position. Nothing to do. + return; + + m_node.start = m_start->value_leaf.key; + m_node.end = m_end->value_leaf.key; + m_node.value = m_start->value_leaf.value; + } + +private: + const typename fst_type::node* m_start; + const typename fst_type::node* m_end; + value_type m_node; +}; + +}}} // namespace mdds::fst::detail + +#endif diff --git a/include/mdds/global.hpp b/include/mdds/global.hpp new file mode 100644 index 0000000..ba5f770 --- /dev/null +++ b/include/mdds/global.hpp @@ -0,0 +1,216 @@ +/************************************************************************* + * + * Copyright (c) 2008-2020 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_GLOBAL_HPP +#define INCLUDED_MDDS_GLOBAL_HPP + +#include +#include +#include +#include +#include + +/** + * \def MDDS_ASCII(literal) + * + * Expands a \a literal string into two arguments: the first one is the + * literal string itself, and the second one is the length of that string. + * + * Note that this macro only works with literal strings defined inline; it + * does not work with pointer values that point to strings defined + * elsewhere. + */ +#define MDDS_ASCII(literal) literal, sizeof(literal) - 1 + +/** + * \def MDDS_N_ELEMENTS(name) + * + * Calculates the length of \a name array provided that the array definition + * is given in the same compilation unit. + * + * @deprecated Please use \c std::size instead. + */ +#define MDDS_N_ELEMENTS(name) sizeof(name) / sizeof(name[0]) + +#ifdef __GNUC__ +#define MDDS_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define MDDS_DEPRECATED __declspec(deprecated) +#else +#define MDDS_DEPRECATED +#endif + +#ifndef MDDS_LOOP_UNROLLING +#define MDDS_LOOP_UNROLLING 1 +#endif + +#ifndef MDDS_USE_OPENMP +#define MDDS_USE_OPENMP 0 +#endif + +#if defined(__AVX__) || defined(__AVX2__) +#ifndef __SSE2__ +#define __SSE2__ 1 +#endif +#endif + +namespace mdds { + +class general_error : public ::std::exception +{ +public: + general_error(const ::std::string& msg) : m_msg(msg) + {} + virtual ~general_error() noexcept + {} + + virtual const char* what() const noexcept + { + return m_msg.c_str(); + } + +private: + ::std::string m_msg; +}; + +class invalid_arg_error : public general_error +{ +public: + invalid_arg_error(const ::std::string& msg) : general_error(msg) + {} +}; + +class size_error : public general_error +{ +public: + size_error(const std::string& msg) : general_error(msg) + {} +}; + +class type_error : public general_error +{ +public: + type_error(const std::string& msg) : general_error(msg) + {} +}; + +class integrity_error : public general_error +{ +public: + integrity_error(const std::string& msg) : general_error(msg) + {} +}; + +namespace detail { + +template +class has_value_type +{ + using y_type = char; + using n_type = long; + + template + static y_type test(typename U::value_type); + template + static n_type test(...); + +public: + static constexpr bool value = sizeof(test(0)) == sizeof(y_type); +}; + +template +struct const_or_not; + +template +struct const_or_not +{ + using type = typename std::add_const::type; +}; + +template +struct const_or_not +{ + using type = T; +}; + +template +using const_t = typename const_or_not>::type; + +template +struct mutable_or_not; + +template +struct mutable_or_not +{ + using type = T; +}; + +template +struct mutable_or_not +{ + using type = typename std::add_const::type; +}; + +template +using mutable_t = typename mutable_or_not>::type; + +template +struct get_iterator_type; + +template +struct get_iterator_type +{ + using type = typename T::const_iterator; +}; + +template +struct get_iterator_type +{ + using type = typename T::iterator; +}; + +template +constexpr bool invalid_static_int() +{ + return false; +} + +template +struct is_complete : std::false_type +{ +}; + +template +struct is_complete> : std::true_type +{ +}; + +} // namespace detail + +} // namespace mdds + +#endif diff --git a/include/mdds/multi_type_matrix.hpp b/include/mdds/multi_type_matrix.hpp new file mode 100644 index 0000000..0cac868 --- /dev/null +++ b/include/mdds/multi_type_matrix.hpp @@ -0,0 +1,844 @@ +/************************************************************************* + * + * Copyright (c) 2012-2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef __MDDS_MULTI_TYPE_MATRIX_HPP__ +#define __MDDS_MULTI_TYPE_MATRIX_HPP__ + +#ifdef MDDS_MULTI_TYPE_MATRIX_DEBUG +#ifndef MDDS_MULTI_TYPE_VECTOR_DEBUG +#define MDDS_MULTI_TYPE_VECTOR_DEBUG 1 +#endif +#endif + +#include "multi_type_vector.hpp" + +namespace mdds { + +namespace mtm { + +/** + * Element type for multi_type_matrix. + */ +enum element_t +{ + element_empty = mdds::mtv::element_type_empty, + element_boolean = mdds::mtv::element_type_boolean, + element_string = mdds::mtv::element_type_string, + element_numeric = mdds::mtv::element_type_double, + element_integer = mdds::mtv::element_type_int32 +}; + +/** + * Default matrix trait that uses std::string as its string type. + */ +struct std_string_traits +{ + typedef mdds::mtv::int32_element_block integer_element_block; + typedef mdds::mtv::string_element_block string_element_block; +}; + +} // namespace mtm + +/** + * Matrix that can store numeric, integer, boolean, empty and string types. + * The string and integer types can be specified in the matrix trait + * template parameter. To use std::string as the string type and int as the + * integer type, use mdds::mtm::std_string_traits. + * + * Internally it uses mdds::multi_type_vector as its value store. The + * element values are linearly stored in column-major order. + */ +template +class multi_type_matrix +{ + typedef Traits traits_type; + +public: + typedef typename traits_type::string_element_block string_block_type; + typedef typename traits_type::integer_element_block integer_block_type; + + typedef typename string_block_type::value_type string_type; + typedef typename integer_block_type::value_type integer_type; + typedef size_t size_type; + +private: + struct mtv_trait : public mdds::mtv::default_traits + { + using block_funcs = mdds::mtv::element_block_funcs< + mdds::mtv::boolean_element_block, mdds::mtv::int8_element_block, mdds::mtv::double_element_block, + typename traits_type::string_element_block, typename traits_type::integer_element_block>; + }; + + using store_type = mdds::multi_type_vector; + +public: + typedef typename store_type::position_type position_type; + typedef typename store_type::const_position_type const_position_type; + + typedef typename store_type::element_block_type element_block_type; + + typedef typename mtv::boolean_element_block boolean_block_type; + typedef typename mtv::double_element_block numeric_block_type; + + struct size_pair_type + { + size_type row; + size_type column; + size_pair_type() : row(0), column(0) + {} + size_pair_type(size_type _row, size_type _column) : row(_row), column(_column) + {} + size_pair_type(std::initializer_list vs) + { + if (vs.size() != 2) + throw invalid_arg_error("size_pair_type requires exactly 2 elements."); + + size_type* ptrs[2] = {&row, &column}; + size_type** p = ptrs; + + for (size_type v : vs) + **p++ = v; + } + + bool operator==(const size_pair_type& r) const + { + return row == r.row && column == r.column; + } + bool operator!=(const size_pair_type& r) const + { + return !operator==(r); + } + }; + + struct element_block_node_type + { + friend class multi_type_matrix; + + mtm::element_t type; + size_type offset; + size_type size; + const element_block_type* data; + + element_block_node_type(); + element_block_node_type(const element_block_node_type& other); + + template + typename _Blk::const_iterator begin() const; + + template + typename _Blk::const_iterator end() const; + + private: + void assign(const const_position_type& pos, size_type section_size); + }; + + static mtm::element_t to_mtm_type(mdds::mtv::element_t mtv_type) + { + switch (mtv_type) + { + case string_block_type::block_type: + return mdds::mtm::element_string; + case integer_block_type::block_type: + return mdds::mtm::element_integer; + case mdds::mtv::element_type_double: + case mdds::mtv::element_type_boolean: + case mdds::mtv::element_type_empty: + // These types share the same numeric values. + return static_cast(mtv_type); + default: + throw type_error("multi_type_matrix: unknown element type."); + } + } + +private: + template + struct walk_func + { + FuncT& m_func; + walk_func(FuncT& func) : m_func(func) + {} + + void operator()(const typename store_type::const_iterator::value_type& mtv_node) + { + element_block_node_type mtm_node; + mtm_node.type = to_mtm_type(mtv_node.type); + mtm_node.size = mtv_node.size; + mtm_node.data = mtv_node.data; + m_func(mtm_node); + } + }; + +public: + /** + * Move to the next logical position. The movement is in the top-to-bottom + * then left-to-right direction. + * + * @param pos position object. + * + * @return position object that references the element at the next logical + * position. + */ + static position_type next_position(const position_type& pos); + + /** + * Move to the next logical position. The movement is in the top-to-bottom + * then left-to-right direction. + * + * @param pos position object. + * + * @return non-mutable position object that references the element at the + * next logical position. + */ + static const_position_type next_position(const const_position_type& pos); + + /** + * Default constructor. + */ + multi_type_matrix(); + + /** + * Construct a matrix of specified size. + * + * @param rows size of rows. + * @param cols size of columns. + */ + multi_type_matrix(size_type rows, size_type cols); + + /** + * Construct a matrix of specified size and initialize all its elements + * with specified value. + * + * @param rows size of rows. + * @param cols size of columns. + * @param value value to initialize all its elements with. + */ + template + multi_type_matrix(size_type rows, size_type cols, const _T& value); + + /** + * Construct a matrix of specified size and initialize its elements with + * specified values. The values are assigned to 2-dimensional matrix + * layout in column-major order. The size of the value array must equal + * rows x cols. + * + * @param rows size of rows. + * @param cols size of columns. + * @param it_begin iterator that points to the value of the first element. + * @param it_end iterator that points to the position after the last + * element value. + */ + template + multi_type_matrix(size_type rows, size_type cols, const _T& it_begin, const _T& it_end); + + /** + * Copy constructor. + */ + multi_type_matrix(const multi_type_matrix& r); + + /** + * Destructor. + */ + ~multi_type_matrix(); + + bool operator==(const multi_type_matrix& other) const; + bool operator!=(const multi_type_matrix& other) const; + + multi_type_matrix& operator=(const multi_type_matrix& r); + + /** + * Get a mutable reference of an element (position object) at specified + * position. The position object can then be passed to an additional + * method to get the type or value of the element it references, or set a + * new value to it. + * + * @param row row position of the referenced element. + * @param col column position of the referenced element. + * + * @return reference object of element at specified position. + */ + position_type position(size_type row, size_type col); + + /** + * Get a mutable reference of an element (position object) at specified + * position. The position object can then be passed to an additional + * method to get the type or value of the element it references, or set a + * new value to it. + * + * @param pos_hint position object to be used as a position hint for + * faster lookup. + * @param row row position of the referenced element. + * @param col column position of the referenced element. + * + * @return reference object of element at specified position. + */ + position_type position(const position_type& pos_hint, size_type row, size_type col); + + /** + * Get an immutable reference of an element (position object) at specified + * position. The position object can then be passed to an additional + * method to get the type or value of the element it references. + * + * @param row row position of the referenced element. + * @param col column position of the referenced element. + * + * @return reference object of element at specified position. + */ + const_position_type position(size_type row, size_type col) const; + + /** + * Get an immutable reference of an element (position object) at specified + * position. The position object can then be passed to an additional + * method to get the type or value of the element it references. + * + * @param pos_hint position object to be used as a position hint for + * faster lookup. + * @param row row position of the referenced element. + * @param col column position of the referenced element. + * + * @return reference object of element at specified position. + */ + const_position_type position(const const_position_type& pos_hint, size_type row, size_type col) const; + + /** + * Get the row and column positions of the current element from a position + * object. + * + * @param pos position object. + * + * @return 0-based row and column positions. + */ + size_pair_type matrix_position(const const_position_type& pos) const; + + /** + * Return a position type that represents an end position. This can be + * used to compare with another position object to see if it is past the + * last element position. + * + * @return end position object. + */ + position_type end_position(); + + /** + * Return a position type that represents an end position. This can be + * used to compare with another position object to see if it is past the + * last element position. + * + * @return end position object. + */ + const_position_type end_position() const; + + /** + * Get the type of element from a position object. The type can be one + * of empty, string, numeric, or boolean. + * + * @param pos position object of an element + * + * @return element type. + */ + mtm::element_t get_type(const const_position_type& pos) const; + + /** + * Get the type of element specified by its position. The type can be one + * of empty, string, numeric, or boolean. + * + * @return element type. + */ + mtm::element_t get_type(size_type row, size_type col) const; + + /** + * Get a numeric representation of the element. If the element is of + * numeric type, its value is returned. If it's of boolean type, either 1 + * or 0 is returned depending on whether it's true or false. If it's of + * empty or string type, 0 is returned. + * + * @param row row position of the element. + * @param col column position of the element. + * + * @return numeric representation of the element. + */ + double get_numeric(size_type row, size_type col) const; + + /** + * Get a numeric representation of the element from a position object. + * If the element is of numeric type, its value is returned. If it's of + * boolean type, either 1 or 0 is returned depending on whether it's true + * or false. If it's of empty or string type, 0 is returned. + * + * @param pos position object of an element + * + * @return numeric representation of the element. + */ + double get_numeric(const const_position_type& pos) const; + + /** + * Get an integer representation of the element. If the element is of + * integer type, its value is returned. If it's of boolean type, either 1 + * or 0 is returned depending on whether it's true or false. If it's of + * empty or string type, 0 is returned. + * + * @param row row position of the element. + * @param col column position of the element. + * + * @return integer representation of the element. + */ + integer_type get_integer(size_type row, size_type col) const; + + /** + * Get an integer representation of the element. If the element is of + * integer type, its value is returned. If it's of boolean type, either 1 + * or 0 is returned depending on whether it's true or false. If it's of + * empty or string type, 0 is returned. + * + * @param pos position object of an element + * + * @return integer representation of the element. + */ + integer_type get_integer(const const_position_type& pos) const; + + /** + * Get a boolean representation of the element. If the element is of + * numeric type, true is returned if it's non-zero, otherwise false is + * returned. If it's of boolean type, its value is returned. If it's of + * empty or string type, false is returned. + * + * @param row row position of the element. + * @param col column position of the element. + * + * @return boolean representation of the element. + */ + bool get_boolean(size_type row, size_type col) const; + + /** + * Get a boolean representation of the element from a position object. + * If the element is of numeric type, true is returned if it's non-zero, + * otherwise false is returned. If it's of boolean type, its value is + * returned. If it's of empty or string type, false is returned. + * + * @param pos position object of an element + * + * @return boolean representation of the element. + */ + bool get_boolean(const const_position_type& pos) const; + + /** + * Get the value of a string element. If the element is not of string + * type, it throws an exception. + * + * @param row row position of the element. + * @param col column position of the element. + * + * @return value of the element. + */ + const string_type& get_string(size_type row, size_type col) const; + + /** + * Get the value of a string element from a position object. If the + * element is not of string type, it throws an exception. + * + * @param pos position object of an element + * + * @return value of the element. + */ + const string_type& get_string(const const_position_type& pos) const; + + /** + * Get the value of element at specified position. The caller must + * explicitly specify the return type. If the element is not of the + * specified type, it throws an exception. + * + * @param row row position of the element. + * @param col column position of the element. + * + * @return value of the element. + */ + template + _T get(size_type row, size_type col) const; + + /** + * Set specified element position empty. + * + * @param row row position of the element. + * @param col column position of the element. + */ + void set_empty(size_type row, size_type col); + + /** + * Set a range of elements empty. The range starts from the position + * specified by the row and col, and extends + * downward first then to the right. + * + * @param row row position of the first element. + * @param col column position of the first element. + * @param length length of the range to set empty. When the length is + * greater than 1, the range extends downward first then to + * the right. + */ + void set_empty(size_type row, size_type col, size_type length); + + /** + * Set element referenced by the position object empty. + * + * @param pos position object that references element. + * + * @return position of the element that has just been made empty. + */ + position_type set_empty(const position_type& pos); + + /** + * Set the entire column empty. + * + * @param col index of the column to empty. + */ + void set_column_empty(size_type col); + + /** + * Set the entire row empty. + * + * @param row index of the row to empty. + */ + void set_row_empty(size_type row); + + /** + * Set a numeric value to an element at specified position. + * + * @param row row index of the element. + * @param col column index of the element. + * @param val new value to set. + */ + void set(size_type row, size_type col, double val); + + /** + * Set a numeric value to an element at specified position. + * + * @param pos position of the element to update. + * @param val new value to set. + * + * @return position of the element block where the new value has been set. + */ + position_type set(const position_type& pos, double val); + + /** + * Set a boolean value to an element at specified position. + * + * @param row row index of the element. + * @param col column index of the element. + * @param val new value to set. + */ + void set(size_type row, size_type col, bool val); + + /** + * Set a boolean value to an element at specified position. + * + * @param pos position of the element to update. + * @param val new value to set. + * + * @return position of the element where the new value has been set. + */ + position_type set(const position_type& pos, bool val); + + /** + * Set a string value to an element at specified position. + * + * @param row row index of the element. + * @param col column index of the element. + * @param str new value to set. + */ + void set(size_type row, size_type col, const string_type& str); + + /** + * Set a string value to an element at specified position. + * + * @param pos position of the element to update. + * @param str new value to set. + * + * @return position of the element block where the new value has been set. + */ + position_type set(const position_type& pos, const string_type& str); + + /** + * Set an integer value to an element at specified position. + * + * @param row row index of the element. + * @param col column index of the element. + * @param val new value to set. + */ + void set(size_type row, size_type col, integer_type val); + + /** + * Set an integer value to an element at specified position. + * + * @param pos position of the element to update. + * @param val new value to set. + * + * @return position of the element block where the new value has been set. + */ + position_type set(const position_type& pos, integer_type val); + + /** + * Set values of multiple elements at once, starting at specified element + * position following the direction of columns. When the new value series + * does not fit in the first column, it gets wrapped into the next + * column(s). + * + *

The method will throw an std::out_of_range exception + * if the specified position is outside the current container range.

+ * + * @param row row position of the start element. + * @param col column position of the start element. + * @param it_begin iterator that points to the begin position of the + * values being set. + * @param it_end iterator that points to the end position of the values + * being set. + */ + template + void set(size_type row, size_type col, const _T& it_begin, const _T& it_end); + + /** + * Set values of multiple elements at once, starting at specified element + * position following the direction of columns. When the new value series + * does not fit in the first column, it gets wrapped into the next + * column(s). + * + * @param pos position of the first element. + * @param it_begin iterator that points to the begin position of the + * values being set. + * @param it_end iterator that points to the end position of the values + * being set. + * + * @return position of the first element that has been modified. + */ + template + position_type set(const position_type& pos, const _T& it_begin, const _T& it_end); + + /** + * Set values of multiple elements at once in a single column. When the + * length of passed elements exceeds that of the column, it gets truncated + * to the column size. + * + * @param col column position + * @param it_begin iterator that points to the begin position of the + * values being set. + * @param it_end iterator that points to the end position of the values + * being set. + */ + template + void set_column(size_type col, const _T& it_begin, const _T& it_end); + + /** + * Return the size of matrix as a pair. The first value is the row size, + * while the second value is the column size. + * + * @return matrix size as a value pair. + */ + size_pair_type size() const; + + /** + * Transpose the stored matrix data. + * + * @return reference to this matrix instance. + */ + multi_type_matrix& transpose(); + + /** + * Copy values from the passed matrix instance. If the size of the passed + * matrix is smaller, then the element values are copied by their + * positions, while the rest of the elements that fall outside the size of + * the passed matrix instance will remain unmodified. If the size of the + * passed matrix instance is larger, then only the elements within the + * size of this matrix instance will get copied. + * + * @param src passed matrix object to copy element values from. + */ + void copy(const multi_type_matrix& src); + + /** + * Copy values from an array or array-like container, to a specified + * sub-matrix range. The length of the array must match the number of + * elements in the destination range, else it will throw a + * mdds::size_error. + * + * @param rows row size of the destination range. + * @param cols column size of the destination range. + * @param it_begin iterator pointing to the beginning of the input array. + * @param it_end iterator pointing to the end position of the input array. + */ + template + void copy(size_type rows, size_type cols, const _T& it_begin, const _T& it_end); + + /** + * Resize the matrix to specified size. This method supports resizing to + * zero-sized matrix; however, either specifying the row or column size to + * zero will resize the matrix to 0 x 0. When resizing the matrix larger, + * empty elements will be inserted in the region where no elements + * existed prior to the call. + * + * @param rows new row size + * @param cols new column size + */ + void resize(size_type rows, size_type cols); + + /** + * Resize the matrix to specified size and initial value. The initial + * value will be applied to new elements created when the specified size + * is larger than the current one. + * + * @param rows new row size + * @param cols new column size + * @param value initial value for new elements + */ + template + void resize(size_type rows, size_type cols, const _T& value); + + /** + * Empty the matrix. + */ + void clear(); + + /** + * Check whether or not this matrix is numeric. A numeric matrix contains + * only numeric or boolean elements. An empty matrix is not numeric. + * + * @return true if the matrix contains only numeric or boolean elements, + * or false otherwise. + */ + bool numeric() const; + + /** + * Check whether or not this matrix is empty. + * + * @return true if this matrix is empty, or false otherwise. + */ + bool empty() const; + + /** + * Swap the content of the matrix with another instance. + */ + void swap(multi_type_matrix& r); + + /** + * Walk all element blocks that consist of the matrix. + * + * @param func function object whose operator() gets called on each + * element block. + * + * @return function object passed to this method. + */ + template + FuncT walk(FuncT func) const; + + /** + * Walk through the element blocks in a sub-matrix range defined by start + * and end positions passed to this method. + * + * @param func function object whose operator() gets called on the + * element block. + * + * @param start the column/row position of the upper-left corner of the + * sub-matrix. + * + * @param end the column/row position of the lower-right corner of the + * sub-matrix. Both column and row must be greater or equal to + * those of the start position. + * + * @return function object passed to this method. + */ + template + FuncT walk(FuncT func, const size_pair_type& start, const size_pair_type& end) const; + + /** + * Walk through all element blocks in parallel with another matrix + * instance. It stops at the block boundaries of both matrix instances + * during the walk. + * + * @param func function object whose operator() gets called on each + * element block. + * + * @param right another matrix instance to parallel-walk with. + */ + template + FuncT walk(FuncT func, const multi_type_matrix& right) const; + + /** + * Walk through the element blocks in a sub-matrix range in parallel with + * another matrix instance. It stops at the block boundaries of both + * matrix instances during the walk. The sub-matrix range is defined by + * start and end positions passed to this method. + * + * @param func function object whose operator() gets called on each + * element block. + * + * @param right another matrix instance to parallel-walk with. + * + * @param start the column/row position of the upper-left corner of the + * sub-matrix. + * + * @param end the column/row position of the lower-right corner of the + * sub-matrix. Both column and row must be greater or equal to + * those of the start position. + */ + template + FuncT walk( + FuncT func, const multi_type_matrix& right, const size_pair_type& start, const size_pair_type& end) const; + +#ifdef MDDS_MULTI_TYPE_MATRIX_DEBUG + void dump() const + { + m_store.dump_blocks(std::cout); + } +#endif + +private: + /** + * Get an array position of the data referenced by the row and column + * indices. The array consists of multiple columns, the content of column + * 0 followed by the content of column 1, and so on. Note that no + * boundary check is performed in this method. + * + * @param row 0-based row index. + * @param col 0-based column index. + * @return position in the data array. + */ + inline size_type get_pos(size_type row, size_type col) const + { + return m_size.row * col + row; + } + + inline size_type get_pos(const const_position_type& pos) const + { + return pos.first->position + pos.second; + } + +private: + store_type m_store; + size_pair_type m_size; +}; + +} // namespace mdds + +#include "multi_type_matrix_def.inl" + +#endif diff --git a/include/mdds/multi_type_matrix_def.inl b/include/mdds/multi_type_matrix_def.inl new file mode 100644 index 0000000..5409071 --- /dev/null +++ b/include/mdds/multi_type_matrix_def.inl @@ -0,0 +1,814 @@ +/************************************************************************* + * + * Copyright (c) 2012-2018 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +namespace mdds { + +template +multi_type_matrix::element_block_node_type::element_block_node_type() + : type(mtm::element_empty), offset(0), size(0), data(nullptr) +{} + +template +multi_type_matrix::element_block_node_type::element_block_node_type(const element_block_node_type& other) + : type(other.type), offset(other.offset), size(other.size), data(other.data) +{} + +template +void multi_type_matrix::element_block_node_type::assign(const const_position_type& pos, size_type section_size) +{ + assert(section_size <= pos.first->size - pos.second); + + type = to_mtm_type(pos.first->type); + offset = pos.second; + size = section_size; + data = pos.first->data; +} + +template +template +typename _Blk::const_iterator multi_type_matrix::element_block_node_type::begin() const +{ + typename _Blk::const_iterator it = _Blk::begin(*data); + std::advance(it, offset); + return it; +} + +template +template +typename _Blk::const_iterator multi_type_matrix::element_block_node_type::end() const +{ + typename _Blk::const_iterator it = _Blk::begin(*data); + std::advance(it, offset + size); + return it; +} + +template +typename multi_type_matrix::position_type multi_type_matrix::next_position(const position_type& pos) +{ + return store_type::next_position(pos); +} + +template +typename multi_type_matrix::const_position_type multi_type_matrix::next_position( + const const_position_type& pos) +{ + return store_type::next_position(pos); +} + +template +multi_type_matrix::multi_type_matrix() : m_size(0, 0) +{} + +template +multi_type_matrix::multi_type_matrix(size_type rows, size_type cols) : m_store(rows * cols), m_size(rows, cols) +{} + +template +template +multi_type_matrix::multi_type_matrix(size_type rows, size_type cols, const _T& value) + : m_store(rows * cols, value), m_size(rows, cols) +{} + +template +template +multi_type_matrix::multi_type_matrix(size_type rows, size_type cols, const _T& it_begin, const _T& it_end) + : m_store(rows * cols, it_begin, it_end), m_size(rows, cols) +{ + if (m_store.empty()) + return; + + // Throw an exception when trying to construct with data that the matrix doesn't support. + typename store_type::iterator it = m_store.begin(); + to_mtm_type(it->type); +} + +template +multi_type_matrix::multi_type_matrix(const multi_type_matrix& r) : m_store(r.m_store), m_size(r.m_size) +{} + +template +multi_type_matrix::~multi_type_matrix() +{} + +template +bool multi_type_matrix::operator==(const multi_type_matrix& r) const +{ + return m_size == r.m_size && m_store == r.m_store; +} + +template +bool multi_type_matrix::operator!=(const multi_type_matrix& r) const +{ + return !operator==(r); +} + +template +multi_type_matrix& multi_type_matrix::operator=(const multi_type_matrix& r) +{ + if (this == &r) + return *this; + + store_type tmp(r.m_store); + m_store.swap(tmp); + m_size = r.m_size; + return *this; +} + +template +typename multi_type_matrix::position_type multi_type_matrix::position(size_type row, size_type col) +{ + return m_store.position(get_pos(row, col)); +} + +template +typename multi_type_matrix::position_type multi_type_matrix::position( + const position_type& pos_hint, size_type row, size_type col) +{ + return m_store.position(pos_hint.first, get_pos(row, col)); +} + +template +typename multi_type_matrix::const_position_type multi_type_matrix::position( + size_type row, size_type col) const +{ + return m_store.position(get_pos(row, col)); +} + +template +typename multi_type_matrix::const_position_type multi_type_matrix::position( + const const_position_type& pos_hint, size_type row, size_type col) const +{ + return m_store.position(pos_hint.first, get_pos(row, col)); +} + +template +typename multi_type_matrix::size_pair_type multi_type_matrix::matrix_position( + const const_position_type& pos) const +{ + size_type mtv_pos = store_type::logical_position(pos); + size_type col = mtv_pos / m_size.row; + size_type row = mtv_pos - m_size.row * col; + return size_pair_type(row, col); +} + +template +typename multi_type_matrix::position_type multi_type_matrix::end_position() +{ + return position_type(m_store.end(), 0); +} + +template +typename multi_type_matrix::const_position_type multi_type_matrix::end_position() const +{ + return const_position_type(m_store.end(), 0); +} + +template +mtm::element_t multi_type_matrix::get_type(const const_position_type& pos) const +{ + return to_mtm_type(pos.first->type); +} + +template +mtm::element_t multi_type_matrix::get_type(size_type row, size_type col) const +{ + return to_mtm_type(m_store.get_type(get_pos(row, col))); +} + +template +double multi_type_matrix::get_numeric(size_type row, size_type col) const +{ + return get_numeric(m_store.position(get_pos(row, col))); +} + +template +double multi_type_matrix::get_numeric(const const_position_type& pos) const +{ + switch (pos.first->type) + { + case mtv::element_type_double: + return mtv::double_element_block::at(*pos.first->data, pos.second); + case integer_block_type::block_type: + return integer_block_type::at(*pos.first->data, pos.second); + case mtv::element_type_boolean: + { + // vector cannot return reference i.e. we can't use at() here. + typename mtv::boolean_element_block::const_iterator it = + mtv::boolean_element_block::begin(*pos.first->data); + std::advance(it, pos.second); + return *it; + } + case string_block_type::block_type: + case mtv::element_type_empty: + return 0.0; + default: + throw general_error("multi_type_matrix: unknown element type."); + } +} + +template +typename multi_type_matrix::integer_type multi_type_matrix::get_integer( + size_type row, size_type col) const +{ + return get_integer(m_store.position(get_pos(row, col))); +} + +template +typename multi_type_matrix::integer_type multi_type_matrix::get_integer( + const const_position_type& pos) const +{ + return static_cast(get_numeric(pos)); +} + +template +bool multi_type_matrix::get_boolean(size_type row, size_type col) const +{ + return static_cast(get_numeric(row, col)); +} + +template +bool multi_type_matrix::get_boolean(const const_position_type& pos) const +{ + return static_cast(get_numeric(pos)); +} + +template +const typename multi_type_matrix::string_type& multi_type_matrix::get_string( + size_type row, size_type col) const +{ + return get_string(m_store.position(get_pos(row, col))); +} + +template +const typename multi_type_matrix::string_type& multi_type_matrix::get_string( + const const_position_type& pos) const +{ + if (pos.first->type != string_block_type::block_type) + throw general_error("multi_type_matrix: unknown element type."); + + return string_block_type::at(*pos.first->data, pos.second); +} + +template +template +_T multi_type_matrix::get(size_type row, size_type col) const +{ + _T val; + m_store.get(get_pos(row, col), val); + return val; +} + +template +void multi_type_matrix::set_empty(size_type row, size_type col) +{ + m_store.set_empty(get_pos(row, col), get_pos(row, col)); +} + +template +void multi_type_matrix::set_empty(size_type row, size_type col, size_type length) +{ + if (length == 0) + throw general_error("multi_type_matrix::set_empty: length of zero is not permitted."); + + size_type pos1 = get_pos(row, col); + m_store.set_empty(pos1, pos1 + length - 1); +} + +template +typename multi_type_matrix::position_type multi_type_matrix::set_empty(const position_type& pos) +{ + size_type store_pos = get_pos(pos); + typename store_type::iterator it = m_store.set_empty(pos.first, store_pos, store_pos); + return position_type(it, store_pos - it->position); +} + +template +void multi_type_matrix::set_column_empty(size_type col) +{ + m_store.set_empty(get_pos(0, col), get_pos(m_size.row - 1, col)); +} + +template +void multi_type_matrix::set_row_empty(size_type row) +{ + for (size_type col = 0; col < m_size.column; ++col) + { + size_type pos = get_pos(row, col); + m_store.set_empty(pos, pos); + } +} + +template +void multi_type_matrix::set(size_type row, size_type col, double val) +{ + m_store.set(get_pos(row, col), val); +} + +template +typename multi_type_matrix::position_type multi_type_matrix::set(const position_type& pos, double val) +{ + size_type store_pos = get_pos(pos); + typename store_type::iterator it = m_store.set(pos.first, store_pos, val); + return position_type(it, store_pos - it->position); +} + +template +void multi_type_matrix::set(size_type row, size_type col, bool val) +{ + m_store.set(get_pos(row, col), val); +} + +template +typename multi_type_matrix::position_type multi_type_matrix::set(const position_type& pos, bool val) +{ + size_type store_pos = get_pos(pos); + typename store_type::iterator it = m_store.set(pos.first, store_pos, val); + return position_type(it, store_pos - it->position); +} + +template +void multi_type_matrix::set(size_type row, size_type col, const string_type& str) +{ + m_store.set(get_pos(row, col), str); +} + +template +typename multi_type_matrix::position_type multi_type_matrix::set( + const position_type& pos, const string_type& str) +{ + size_type store_pos = get_pos(pos); + typename store_type::iterator it = m_store.set(pos.first, store_pos, str); + return position_type(it, store_pos - it->position); +} + +template +void multi_type_matrix::set(size_type row, size_type col, integer_type val) +{ + m_store.set(get_pos(row, col), val); +} + +template +typename multi_type_matrix::position_type multi_type_matrix::set( + const position_type& pos, integer_type val) +{ + size_type store_pos = get_pos(pos); + typename store_type::iterator it = m_store.set(pos.first, store_pos, val); + return position_type(it, store_pos - it->position); +} + +template +template +void multi_type_matrix::set(size_type row, size_type col, const _T& it_begin, const _T& it_end) +{ + m_store.set(get_pos(row, col), it_begin, it_end); +} + +template +template +typename multi_type_matrix::position_type multi_type_matrix::set( + const position_type& pos, const _T& it_begin, const _T& it_end) +{ + size_type store_pos = get_pos(pos); + typename store_type::iterator it = m_store.set(pos.first, store_pos, it_begin, it_end); + return position_type(it, store_pos - it->position); +} + +template +template +void multi_type_matrix::set_column(size_type col, const _T& it_begin, const _T& it_end) +{ + size_type pos = get_pos(0, col); + size_type len = std::distance(it_begin, it_end); + + if (len <= m_size.row) + { + m_store.set(pos, it_begin, it_end); + return; + } + + _T it_end2 = it_begin; + std::advance(it_end2, m_size.row); + m_store.set(pos, it_begin, it_end2); +} + +template +typename multi_type_matrix::size_pair_type multi_type_matrix::size() const +{ + return m_size; +} + +template +multi_type_matrix& multi_type_matrix::transpose() +{ + multi_type_matrix tmp(m_size.column, m_size.row); + for (size_type old_row_new_col = 0; old_row_new_col < m_size.row; ++old_row_new_col) + { + for (size_type old_col_new_row = 0; old_col_new_row < m_size.column; ++old_col_new_row) + { + switch (get_type(old_row_new_col, old_col_new_row)) + { + case mtm::element_numeric: + { + double val; + m_store.get(get_pos(old_row_new_col, old_col_new_row), val); + tmp.set(old_col_new_row, old_row_new_col, val); + } + break; + case mtm::element_boolean: + { + bool val; + m_store.get(get_pos(old_row_new_col, old_col_new_row), val); + tmp.set(old_col_new_row, old_row_new_col, val); + } + break; + case mtm::element_string: + { + string_type val; + m_store.get(get_pos(old_row_new_col, old_col_new_row), val); + tmp.set(old_col_new_row, old_row_new_col, val); + } + break; + case mtm::element_empty: + break; + default: + throw general_error("multi_type_matrix: unknown element type."); + } + } + } + + swap(tmp); + return *this; +} + +template +void multi_type_matrix::copy(const multi_type_matrix& src) +{ + if (&src == this) + // Self assignment. + return; + + if (empty() || src.empty()) + return; + + size_type rows = std::min(m_size.row, src.m_size.row); + size_type cols = std::min(m_size.column, src.m_size.column); + + position_type pos_dest = position(0, 0); + const_position_type pos_src = src.position(0, 0); + + element_block_node_type src_node; + + for (size_t col = 0; col < cols; ++col) + { + pos_dest = position(pos_dest, 0, col); + pos_src = src.position(pos_src, 0, col); + + size_t remaining_rows = rows; + + do + { + size_type src_blk_left = pos_src.first->size - pos_src.second; + size_type section_size = std::min(src_blk_left, remaining_rows); + src_node.assign(pos_src, section_size); + + size_type logical_pos_dest = store_type::logical_position(pos_dest); + + typename store_type::iterator blk_pos; + + switch (to_mtm_type(pos_src.first->type)) + { + case mtm::element_numeric: + { + auto it = src_node.template begin(); + auto ite = src_node.template end(); + + blk_pos = m_store.set(pos_dest.first, logical_pos_dest, it, ite); + } + break; + case mtm::element_boolean: + { + auto it = src_node.template begin(); + auto ite = src_node.template end(); + + blk_pos = m_store.set(pos_dest.first, logical_pos_dest, it, ite); + } + break; + case mtm::element_string: + { + auto it = src_node.template begin(); + auto ite = src_node.template end(); + + blk_pos = m_store.set(pos_dest.first, logical_pos_dest, it, ite); + } + break; + case mtm::element_empty: + { + size_type end = logical_pos_dest + section_size - 1; + blk_pos = m_store.set_empty(pos_dest.first, logical_pos_dest, end); + } + break; + default: + throw general_error("multi_type_matrix: unknown element type."); + } + + remaining_rows -= section_size; + + size_type logical_pos_next = logical_pos_dest + section_size; + if (logical_pos_next >= m_store.size()) + // No more room left in the destination store. Bail out. + return; + + pos_dest = m_store.position(blk_pos, logical_pos_next); + + // Move source to the head of the next block in the column. + pos_src = const_position_type(++pos_src.first, 0); + } while (remaining_rows); + } +} + +template +template +void multi_type_matrix::copy(size_type rows, size_type cols, const _T& it_begin, const _T& it_end) +{ + size_t n = std::distance(it_begin, it_end); + if (!n || empty()) + return; + + if (n != rows * cols) + throw size_error("multi_type_matrix: size of the array does not match the destination size."); + + if (rows > m_size.row || cols > m_size.column) + throw size_error("multi_type_matrix: specified destination size is larger than the current matrix."); + + // Ensure that the passed array is supported by this matrix. + to_mtm_type(store_type::get_element_type(*it_begin)); + + auto it = it_begin; + position_type pos_dest = position(0, 0); + + for (size_t col = 0; col < cols; ++col) + { + pos_dest = position(pos_dest, 0, col); + + auto it_this_end = it; + std::advance(it_this_end, rows); + + pos_dest.first = m_store.set(pos_dest.first, get_pos(0, col), it, it_this_end); + it = it_this_end; + } +} + +template +void multi_type_matrix::resize(size_type rows, size_type cols) +{ + if (!rows || !cols) + { + m_size.row = 0; + m_size.column = 0; + m_store.clear(); + return; + } + + multi_type_matrix temp(rows, cols); + temp.copy(*this); + temp.swap(*this); +} + +template +template +void multi_type_matrix::resize(size_type rows, size_type cols, const _T& value) +{ + if (!rows || !cols) + { + m_size.row = 0; + m_size.column = 0; + m_store.clear(); + return; + } + + multi_type_matrix temp(rows, cols, value); + temp.copy(*this); + temp.swap(*this); +} + +template +void multi_type_matrix::clear() +{ + m_store.clear(); + m_size.row = 0; + m_size.column = 0; +} + +template +bool multi_type_matrix::numeric() const +{ + if (m_store.empty()) + return false; + + typename store_type::const_iterator i = m_store.begin(), iend = m_store.end(); + for (; i != iend; ++i) + { + mtv::element_t mtv_type = i->type; + switch (mtv_type) + { + case mtv::element_type_double: + case mtv::element_type_boolean: + case integer_block_type::block_type: + // These are numeric types. + continue; + case string_block_type::block_type: + case mtv::element_type_empty: + // These are not. + return false; + default: + throw general_error("multi_type_matrix: unknown element type."); + } + } + + return true; +} + +template +bool multi_type_matrix::empty() const +{ + return m_store.empty(); +} + +template +void multi_type_matrix::swap(multi_type_matrix& r) +{ + m_store.swap(r.m_store); + std::swap(m_size.row, r.m_size.row); + std::swap(m_size.column, r.m_size.column); +} + +template +template +FuncT multi_type_matrix::walk(FuncT func) const +{ + walk_func wf(func); + std::for_each(m_store.begin(), m_store.end(), wf); + return func; +} + +template +template +FuncT multi_type_matrix::walk(FuncT func, const size_pair_type& start, const size_pair_type& end) const +{ + if (end.row < start.row || end.column < start.column) + { + std::ostringstream os; + os << "multi_type_matrix: invalid start/end position pair: (row=" << start.row << "; column=" << start.column + << ") - (row=" << end.row << "; column=" << end.column << ")"; + throw size_error(os.str()); + } + + if (end.row > m_size.row || end.column > m_size.column) + throw size_error("multi_type_matrix: end position is out-of-bound."); + + size_t rows = end.row - start.row + 1; + element_block_node_type mtm_node; + const_position_type pos = position(0, 0); + + // we need to handle columns manually, as the columns are continuously in memory. + // To go from one column to the next we need to jump in the memory. + for (size_t col = start.column; col <= end.column; ++col) + { + pos = position(pos, start.row, col); + size_t remaining_rows = rows; + + do + { + size_type remaining_blk = pos.first->size - pos.second; + + // handle the two possible cases: + // 1.) the current block is completely contained in our selection + // 2.) the current block contains the end of the selection + + size_type section_size = std::min(remaining_blk, remaining_rows); + mtm_node.assign(pos, section_size); + + remaining_rows -= section_size; + func(mtm_node); + + // Move to the head of the next block in the column. + pos = const_position_type(++pos.first, 0); + } while (remaining_rows != 0); + } + + return func; +} + +template +template +FuncT multi_type_matrix::walk(FuncT func, const multi_type_matrix& right) const +{ + if (size() != right.size()) + throw size_error("multi_type_matrix: left and right matrices must have the same geometry."); + + if (m_store.empty()) + return func; + + size_t remaining_size = m_store.size(); + + typename store_type::const_iterator it1 = m_store.begin(); + typename store_type::const_iterator it2 = right.m_store.begin(); + const_position_type pos1(it1, 0), pos2(it2, 0); + element_block_node_type node1, node2; + + while (remaining_size) + { + size_t section_size = std::min(pos1.first->size - pos1.second, pos2.first->size - pos2.second); + + node1.assign(pos1, section_size); + node2.assign(pos2, section_size); + + func(node1, node2); + + pos1 = store_type::advance_position(pos1, section_size); + pos2 = store_type::advance_position(pos2, section_size); + + remaining_size -= section_size; + } + + return func; +} + +template +template +FuncT multi_type_matrix::walk( + FuncT func, const multi_type_matrix& right, const size_pair_type& start, const size_pair_type& end) const +{ + if (end.row < start.row || end.column < start.column) + { + std::ostringstream os; + os << "multi_type_matrix: invalid start/end position pair: (row=" << start.row << "; column=" << start.column + << ") - (row=" << end.row << "; column=" << end.column << ")"; + throw size_error(os.str()); + } + + if (end.row > m_size.row || end.column > m_size.column || end.row > right.size().row || + end.column > right.size().column) + throw size_error("multi_type_matrix: end position is out-of-bound."); + + size_t rows = end.row - start.row + 1; + + element_block_node_type node1, node2; + const_position_type pos1 = position(0, 0), pos2 = right.position(0, 0); + + for (size_t col = start.column; col <= end.column; ++col) + { + pos1 = position(pos1, start.row, col); + pos2 = right.position(pos2, start.row, col); + + size_t remaining_rows = rows; + + do + { + size_type blk1_left = pos1.first->size - pos1.second; + size_type blk2_left = pos2.first->size - pos2.second; + + // Section size should be the smallest of blk1_left, blk2_left and remaining_rows. + size_type section_size = std::min(blk1_left, blk2_left); + section_size = std::min(section_size, remaining_rows); + + node1.assign(pos1, section_size); + node2.assign(pos2, section_size); + + func(node1, node2); + + pos1 = store_type::advance_position(pos1, section_size); + pos2 = store_type::advance_position(pos2, section_size); + + remaining_rows -= section_size; + } while (remaining_rows); + } + + return func; +} + +} // namespace mdds diff --git a/include/mdds/multi_type_vector.hpp b/include/mdds/multi_type_vector.hpp new file mode 100644 index 0000000..0ee7b54 --- /dev/null +++ b/include/mdds/multi_type_vector.hpp @@ -0,0 +1,44 @@ +/************************************************************************* + * + * Copyright (c) 2011-2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_HPP + +#include "./multi_type_vector/soa/main.hpp" + +namespace mdds { + +/** + * Type alias for the concrete implementation to maintain backward API + * compatibility. + */ +template +using multi_type_vector = mtv::soa::multi_type_vector; + +} // namespace mdds + +#endif diff --git a/include/mdds/multi_type_vector/Makefile.am b/include/mdds/multi_type_vector/Makefile.am new file mode 100644 index 0000000..29e26be --- /dev/null +++ b/include/mdds/multi_type_vector/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS = aos soa + +headersdir = $(includedir)/mdds-@API_VERSION@/mdds/multi_type_vector + +headers_HEADERS = \ + block_funcs.hpp \ + collection_def.inl \ + collection.hpp \ + env.hpp \ + iterator_node.hpp \ + macro.hpp \ + standard_element_blocks.hpp \ + types.hpp \ + types_util.hpp \ + util.hpp + diff --git a/include/mdds/multi_type_vector/Makefile.in b/include/mdds/multi_type_vector/Makefile.in new file mode 100644 index 0000000..e53e42d --- /dev/null +++ b/include/mdds/multi_type_vector/Makefile.in @@ -0,0 +1,707 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = include/mdds/multi_type_vector +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \ + $(top_srcdir)/m4/m4_ax_valgrind_check.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(headers_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(headersdir)" +HEADERS = $(headers_HEADERS) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__extra_recursive_targets = check-valgrind-recursive \ + check-valgrind-memcheck-recursive \ + check-valgrind-helgrind-recursive check-valgrind-drd-recursive \ + check-valgrind-sgcheck-recursive +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +API_VERSION = @API_VERSION@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_NODEBUG = @CPPFLAGS_NODEBUG@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CXX = @CXX@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CXXFLAGS_UNITTESTS = @CXXFLAGS_UNITTESTS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DOXYGEN = @DOXYGEN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +ENABLE_VALGRIND_drd = @ENABLE_VALGRIND_drd@ +ENABLE_VALGRIND_helgrind = @ENABLE_VALGRIND_helgrind@ +ENABLE_VALGRIND_memcheck = @ENABLE_VALGRIND_memcheck@ +ENABLE_VALGRIND_sgcheck = @ENABLE_VALGRIND_sgcheck@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +EXPECT = @EXPECT@ +GDB = @GDB@ +HAVE_CXX17 = @HAVE_CXX17@ +INCDIR = @INCDIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MISCDIR = @MISCDIR@ +MKDIR_P = @MKDIR_P@ +OBJDIR = @OBJDIR@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +QUICKCHECKDIR = @QUICKCHECKDIR@ +RUNTEST_BIN = @RUNTEST_BIN@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINX = @SPHINX@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VALGRIND_ENABLED = @VALGRIND_ENABLED@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +valgrind_enabled_tools = @valgrind_enabled_tools@ +valgrind_tools = @valgrind_tools@ +SUBDIRS = aos soa +headersdir = $(includedir)/mdds-@API_VERSION@/mdds/multi_type_vector +headers_HEADERS = \ + block_funcs.hpp \ + collection_def.inl \ + collection.hpp \ + env.hpp \ + iterator_node.hpp \ + macro.hpp \ + standard_element_blocks.hpp \ + types.hpp \ + types_util.hpp \ + util.hpp + +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/mdds/multi_type_vector/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign include/mdds/multi_type_vector/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-headersHEADERS: $(headers_HEADERS) + @$(NORMAL_INSTALL) + @list='$(headers_HEADERS)'; test -n "$(headersdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(headersdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(headersdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(headersdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(headersdir)" || exit $$?; \ + done + +uninstall-headersHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(headers_HEADERS)'; test -n "$(headersdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(headersdir)'; $(am__uninstall_files_from_dir) + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" +check-valgrind-local: +check-valgrind-memcheck-local: +check-valgrind-helgrind-local: +check-valgrind-drd-local: +check-valgrind-sgcheck-local: + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile $(HEADERS) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(headersdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +check-valgrind: check-valgrind-recursive + +check-valgrind-am: check-valgrind-local + +check-valgrind-drd: check-valgrind-drd-recursive + +check-valgrind-drd-am: check-valgrind-drd-local + +check-valgrind-helgrind: check-valgrind-helgrind-recursive + +check-valgrind-helgrind-am: check-valgrind-helgrind-local + +check-valgrind-memcheck: check-valgrind-memcheck-recursive + +check-valgrind-memcheck-am: check-valgrind-memcheck-local + +check-valgrind-sgcheck: check-valgrind-sgcheck-recursive + +check-valgrind-sgcheck-am: check-valgrind-sgcheck-local + +clean: clean-recursive + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-headersHEADERS + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-headersHEADERS + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am check-valgrind-am check-valgrind-drd-am \ + check-valgrind-drd-local check-valgrind-helgrind-am \ + check-valgrind-helgrind-local check-valgrind-local \ + check-valgrind-memcheck-am check-valgrind-memcheck-local \ + check-valgrind-sgcheck-am check-valgrind-sgcheck-local clean \ + clean-generic cscopelist-am ctags ctags-am distclean \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-headersHEADERS install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \ + tags-am uninstall uninstall-am uninstall-headersHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/include/mdds/multi_type_vector/aos/Makefile.am b/include/mdds/multi_type_vector/aos/Makefile.am new file mode 100644 index 0000000..0315e43 --- /dev/null +++ b/include/mdds/multi_type_vector/aos/Makefile.am @@ -0,0 +1,8 @@ + +headersdir = $(includedir)/mdds-@API_VERSION@/mdds/multi_type_vector/aos + +headers_HEADERS = \ + block_util.hpp \ + iterator.hpp \ + main_def.inl \ + main.hpp diff --git a/include/mdds/multi_type_vector/aos/Makefile.in b/include/mdds/multi_type_vector/aos/Makefile.in new file mode 100644 index 0000000..60644e6 --- /dev/null +++ b/include/mdds/multi_type_vector/aos/Makefile.in @@ -0,0 +1,585 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = include/mdds/multi_type_vector/aos +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \ + $(top_srcdir)/m4/m4_ax_valgrind_check.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(headers_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(headersdir)" +HEADERS = $(headers_HEADERS) +am__extra_recursive_targets = check-valgrind-recursive \ + check-valgrind-memcheck-recursive \ + check-valgrind-helgrind-recursive check-valgrind-drd-recursive \ + check-valgrind-sgcheck-recursive +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +API_VERSION = @API_VERSION@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_NODEBUG = @CPPFLAGS_NODEBUG@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CXX = @CXX@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CXXFLAGS_UNITTESTS = @CXXFLAGS_UNITTESTS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DOXYGEN = @DOXYGEN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +ENABLE_VALGRIND_drd = @ENABLE_VALGRIND_drd@ +ENABLE_VALGRIND_helgrind = @ENABLE_VALGRIND_helgrind@ +ENABLE_VALGRIND_memcheck = @ENABLE_VALGRIND_memcheck@ +ENABLE_VALGRIND_sgcheck = @ENABLE_VALGRIND_sgcheck@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +EXPECT = @EXPECT@ +GDB = @GDB@ +HAVE_CXX17 = @HAVE_CXX17@ +INCDIR = @INCDIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MISCDIR = @MISCDIR@ +MKDIR_P = @MKDIR_P@ +OBJDIR = @OBJDIR@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +QUICKCHECKDIR = @QUICKCHECKDIR@ +RUNTEST_BIN = @RUNTEST_BIN@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINX = @SPHINX@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VALGRIND_ENABLED = @VALGRIND_ENABLED@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +valgrind_enabled_tools = @valgrind_enabled_tools@ +valgrind_tools = @valgrind_tools@ +headersdir = $(includedir)/mdds-@API_VERSION@/mdds/multi_type_vector/aos +headers_HEADERS = \ + block_util.hpp \ + iterator.hpp \ + main_def.inl \ + main.hpp + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/mdds/multi_type_vector/aos/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign include/mdds/multi_type_vector/aos/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-headersHEADERS: $(headers_HEADERS) + @$(NORMAL_INSTALL) + @list='$(headers_HEADERS)'; test -n "$(headersdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(headersdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(headersdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(headersdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(headersdir)" || exit $$?; \ + done + +uninstall-headersHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(headers_HEADERS)'; test -n "$(headersdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(headersdir)'; $(am__uninstall_files_from_dir) +check-valgrind-local: +check-valgrind-memcheck-local: +check-valgrind-helgrind-local: +check-valgrind-drd-local: +check-valgrind-sgcheck-local: + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(headersdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +check-valgrind: check-valgrind-am + +check-valgrind-am: check-valgrind-local + +check-valgrind-drd: check-valgrind-drd-am + +check-valgrind-drd-am: check-valgrind-drd-local + +check-valgrind-helgrind: check-valgrind-helgrind-am + +check-valgrind-helgrind-am: check-valgrind-helgrind-local + +check-valgrind-memcheck: check-valgrind-memcheck-am + +check-valgrind-memcheck-am: check-valgrind-memcheck-local + +check-valgrind-sgcheck: check-valgrind-sgcheck-am + +check-valgrind-sgcheck-am: check-valgrind-sgcheck-local + +clean: clean-am + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-headersHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-headersHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am check-valgrind-am \ + check-valgrind-drd-am check-valgrind-drd-local \ + check-valgrind-helgrind-am check-valgrind-helgrind-local \ + check-valgrind-local check-valgrind-memcheck-am \ + check-valgrind-memcheck-local check-valgrind-sgcheck-am \ + check-valgrind-sgcheck-local clean clean-generic cscopelist-am \ + ctags ctags-am distclean distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am \ + install-headersHEADERS install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic pdf \ + pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-headersHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/include/mdds/multi_type_vector/aos/block_util.hpp b/include/mdds/multi_type_vector/aos/block_util.hpp new file mode 100644 index 0000000..4f13eed --- /dev/null +++ b/include/mdds/multi_type_vector/aos/block_util.hpp @@ -0,0 +1,241 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_AOS_BLOCK_UTIL_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_AOS_BLOCK_UTIL_HPP + +#include "mdds/global.hpp" +#include "../types.hpp" + +namespace mdds { namespace mtv { namespace aos { namespace detail { + +template +struct adjust_block_positions +{ + void operator()(Blks& blocks, int64_t start_block_index, int64_t delta) const + { + static_assert( + mdds::detail::invalid_static_int, "The loop-unrolling factor must be one of 0, 4, 8, 16, or 32."); + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& blocks, int64_t start_block_index, int64_t delta) const + { + int64_t n = blocks.size(); + + if (start_block_index >= n) + return; + +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < n; ++i) + blocks[i].position += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& blocks, int64_t start_block_index, int64_t delta) const + { + int64_t n = blocks.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 4. + int64_t len = n - start_block_index; + int64_t rem = len & 3; // % 4 + len -= rem; + len += start_block_index; +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 4) + { + blocks[i].position += delta; + blocks[i + 1].position += delta; + blocks[i + 2].position += delta; + blocks[i + 3].position += delta; + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + blocks[i].position += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& blocks, int64_t start_block_index, int64_t delta) const + { + int64_t n = blocks.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 8. + int64_t len = n - start_block_index; + int64_t rem = len & 7; // % 8 + len -= rem; + len += start_block_index; +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 8) + { + blocks[i].position += delta; + blocks[i + 1].position += delta; + blocks[i + 2].position += delta; + blocks[i + 3].position += delta; + blocks[i + 4].position += delta; + blocks[i + 5].position += delta; + blocks[i + 6].position += delta; + blocks[i + 7].position += delta; + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + blocks[i].position += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& blocks, int64_t start_block_index, int64_t delta) const + { + int64_t n = blocks.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 16. + int64_t len = n - start_block_index; + int64_t rem = len & 15; // % 16 + len -= rem; + len += start_block_index; +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 16) + { + blocks[i].position += delta; + blocks[i + 1].position += delta; + blocks[i + 2].position += delta; + blocks[i + 3].position += delta; + blocks[i + 4].position += delta; + blocks[i + 5].position += delta; + blocks[i + 6].position += delta; + blocks[i + 7].position += delta; + blocks[i + 8].position += delta; + blocks[i + 9].position += delta; + blocks[i + 10].position += delta; + blocks[i + 11].position += delta; + blocks[i + 12].position += delta; + blocks[i + 13].position += delta; + blocks[i + 14].position += delta; + blocks[i + 15].position += delta; + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + blocks[i].position += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& blocks, int64_t start_block_index, int64_t delta) const + { + int64_t n = blocks.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 32. + int64_t len = n - start_block_index; + int64_t rem = len & 31; // % 32 + len -= rem; + len += start_block_index; +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 32) + { + blocks[i].position += delta; + blocks[i + 1].position += delta; + blocks[i + 2].position += delta; + blocks[i + 3].position += delta; + blocks[i + 4].position += delta; + blocks[i + 5].position += delta; + blocks[i + 6].position += delta; + blocks[i + 7].position += delta; + blocks[i + 8].position += delta; + blocks[i + 9].position += delta; + blocks[i + 10].position += delta; + blocks[i + 11].position += delta; + blocks[i + 12].position += delta; + blocks[i + 13].position += delta; + blocks[i + 14].position += delta; + blocks[i + 15].position += delta; + blocks[i + 16].position += delta; + blocks[i + 17].position += delta; + blocks[i + 18].position += delta; + blocks[i + 19].position += delta; + blocks[i + 20].position += delta; + blocks[i + 21].position += delta; + blocks[i + 22].position += delta; + blocks[i + 23].position += delta; + blocks[i + 24].position += delta; + blocks[i + 25].position += delta; + blocks[i + 26].position += delta; + blocks[i + 27].position += delta; + blocks[i + 28].position += delta; + blocks[i + 29].position += delta; + blocks[i + 30].position += delta; + blocks[i + 31].position += delta; + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + blocks[i].position += delta; + } +}; + +}}}} // namespace mdds::mtv::aos::detail + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/aos/iterator.hpp b/include/mdds/multi_type_vector/aos/iterator.hpp new file mode 100644 index 0000000..619d630 --- /dev/null +++ b/include/mdds/multi_type_vector/aos/iterator.hpp @@ -0,0 +1,303 @@ +/************************************************************************* + * + * Copyright (c) 2012-2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_AOS_ITERATOR_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_AOS_ITERATOR_HPP + +#include "../iterator_node.hpp" + +#include + +namespace mdds { namespace mtv { namespace aos { namespace detail { + +/** + * Common base for both const and non-const iterators. Its protected inc() + * and dec() methods have non-const return type, and the derived classes + * wrap them and return values with their respective const modifiers. + */ +template +class iterator_common_base +{ +protected: + typedef typename Traits::parent parent_type; + typedef typename Traits::blocks blocks_type; + typedef typename Traits::base_iterator base_iterator_type; + + typedef typename parent_type::size_type size_type; + typedef mdds::detail::mtv::iterator_value_node node; + + iterator_common_base() : m_cur_node(nullptr, 0) + {} + + iterator_common_base( + const base_iterator_type& pos, const base_iterator_type& end, const parent_type* parent, size_type block_index) + : m_cur_node(parent, block_index), m_pos(pos), m_end(end) + { + if (m_pos != m_end) + update_node(); + } + + iterator_common_base(const iterator_common_base& other) + : m_cur_node(other.m_cur_node), m_pos(other.m_pos), m_end(other.m_end) + {} + + void update_node() + { +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + if (m_pos == m_end) + throw general_error("Current node position should never equal the end position during node update."); +#endif + // blocks_type::value_type is a pointer to multi_type_vector::block. + const typename blocks_type::value_type& blk = *m_pos; + if (blk.data) + m_cur_node.type = mdds::mtv::get_block_type(*blk.data); + else + m_cur_node.type = mdds::mtv::element_type_empty; + + m_cur_node.position = blk.position; + m_cur_node.size = blk.size; + m_cur_node.data = blk.data; + } + + node* inc() + { + ++m_pos; + if (m_pos == m_end) + return nullptr; + + update_node(); + return &m_cur_node; + } + + node* dec() + { + --m_pos; + update_node(); + return &m_cur_node; + } + + node m_cur_node; + base_iterator_type m_pos; + base_iterator_type m_end; + +public: + bool operator==(const iterator_common_base& other) const + { + if (m_pos != m_end && other.m_pos != other.m_end) + { + // TODO: Set hard-coded values to the current node for the end + // position nodes to remove this if block. + if (m_cur_node != other.m_cur_node) + return false; + } + return m_pos == other.m_pos && m_end == other.m_end; + } + + bool operator!=(const iterator_common_base& other) const + { + return !operator==(other); + } + + iterator_common_base& operator=(const iterator_common_base& other) + { + m_cur_node = other.m_cur_node; + m_pos = other.m_pos; + m_end = other.m_end; + return *this; + } + + void swap(iterator_common_base& other) + { + m_cur_node.swap(other.m_cur_node); + std::swap(m_pos, other.m_pos); + std::swap(m_end, other.m_end); + } + + const node& get_node() const + { + return m_cur_node; + } + const base_iterator_type& get_pos() const + { + return m_pos; + } + const base_iterator_type& get_end() const + { + return m_end; + } +}; + +template +class iterator_base : public iterator_common_base +{ + using parent_type = typename Traits::parent; + typedef NodeUpdateFunc node_update_func; + typedef iterator_common_base common_base; + + typedef typename Traits::base_iterator base_iterator_type; + typedef typename common_base::size_type size_type; + + using common_base::dec; + using common_base::inc; + using common_base::m_cur_node; + +public: + using common_base::get_end; + using common_base::get_pos; + + // iterator traits + typedef typename common_base::node value_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + +public: + iterator_base() + {} + iterator_base( + const base_iterator_type& pos, const base_iterator_type& end, const parent_type* parent, size_type block_index) + : common_base(pos, end, parent, block_index) + {} + + value_type& operator*() + { + return m_cur_node; + } + + const value_type& operator*() const + { + return m_cur_node; + } + + value_type* operator->() + { + return &m_cur_node; + } + + const value_type* operator->() const + { + return &m_cur_node; + } + + iterator_base& operator++() + { + node_update_func::inc(m_cur_node); + inc(); + return *this; + } + + iterator_base& operator--() + { + dec(); + node_update_func::dec(m_cur_node); + return *this; + } +}; + +template +class const_iterator_base : public iterator_common_base +{ + using parent_type = typename Traits::parent; + typedef NodeUpdateFunc node_update_func; + typedef iterator_common_base common_base; + + typedef typename Traits::base_iterator base_iterator_type; + typedef typename common_base::size_type size_type; + + using common_base::dec; + using common_base::inc; + using common_base::m_cur_node; + +public: + using common_base::get_end; + using common_base::get_pos; + + typedef NonConstItrBase iterator_base; + + // iterator traits + typedef typename common_base::node value_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + +public: + const_iterator_base() : common_base() + {} + const_iterator_base( + const base_iterator_type& pos, const base_iterator_type& end, const parent_type* parent, size_type block_index) + : common_base(pos, end, parent, block_index) + {} + + /** + * Take the non-const iterator counterpart to create a const iterator. + */ + const_iterator_base(const iterator_base& other) + : common_base( + other.get_pos(), other.get_end(), other.get_node().__private_data.parent, + other.get_node().__private_data.block_index) + {} + + const value_type& operator*() const + { + return m_cur_node; + } + + const value_type* operator->() const + { + return &m_cur_node; + } + + const_iterator_base& operator++() + { + node_update_func::inc(m_cur_node); + inc(); + return *this; + } + + const_iterator_base& operator--() + { + dec(); + node_update_func::dec(m_cur_node); + return *this; + } + + bool operator==(const const_iterator_base& other) const + { + return iterator_common_base::operator==(other); + } + + bool operator!=(const const_iterator_base& other) const + { + return iterator_common_base::operator!=(other); + } +}; + +}}}} // namespace mdds::mtv::aos::detail + +#endif diff --git a/include/mdds/multi_type_vector/aos/main.hpp b/include/mdds/multi_type_vector/aos/main.hpp new file mode 100644 index 0000000..667a1b0 --- /dev/null +++ b/include/mdds/multi_type_vector/aos/main.hpp @@ -0,0 +1,1335 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_AOS_MAIN_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_AOS_MAIN_HPP + +#include "../../global.hpp" +#include "../types.hpp" +#include "../util.hpp" +#include "./iterator.hpp" +#include "./block_util.hpp" + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG +#include +#endif + +namespace mdds { namespace mtv { namespace aos { + +/** + * Multi-type vector consists of a series of one or more blocks, and each + * block may either be empty, or stores a series of non-empty elements of + * identical type. These blocks collectively represent a single logical + * one-dimensional array that may store elements of different types. It is + * guaranteed that the block types of neighboring blocks are always + * different. + * + * Structurally, the primary array stores block instances whose types + * are of value_type, which in turn consists of the following + * data members: + * + *
    + *
  • type which indicates the block type,
  • + *
  • position which stores the logical position of the + * first element of the block,
  • + *
  • size which stores the logical size of the block, + * and
  • + *
  • data which stores the pointer to a secondary array + * (a.k.a. element block) which stores the actual element values, or + * nullptr in case the block represents an empty segment.
  • + *
+ * + * This variant implements an array-of-structures (AoS) storage. + * + * @see mdds::mtv::aos::multi_type_vector::value_type + */ +template +class multi_type_vector +{ +public: + typedef size_t size_type; + + typedef mdds::mtv::base_element_block element_block_type; + typedef mdds::mtv::element_t element_category_type; + using block_funcs = typename Traits::block_funcs; + + /** + * Optional event handler function structure, whose functions get called + * at specific events. The following events are currently supported: + * + *
    + *
  • element_block_acquired - this gets called whenever + * the container acquires a new element block either as a result of a new + * element block creation or a tranfer of an existing + * element block from another container.
  • + *
  • element_block_released - this gets called whenever + * the container releases an existing element block either because + * the block gets deleted or gets transferred to another container.
  • + *
+ * + * @see mdds::mtv::empty_event_func for the precise function signatures of + * the event handler functions. + */ + using event_func = typename Traits::event_func; + +private: + struct block + { + size_type position; + size_type size; + element_block_type* data; + + block(); + block(size_type _position, size_type _size); + block(size_type _position, size_type _size, element_block_type* _data); + block(const block& other) = default; + block(block&& other) = default; + ~block() = default; + void swap(block& other); + void clone_to(block& other) const; + + block& operator=(const block&) = default; + }; + + struct element_block_deleter + { + void operator()(const element_block_type* p) + { + block_funcs::delete_block(p); + } + }; + + typedef std::vector blocks_type; + + struct blocks_to_transfer + { + blocks_type blocks; + size_type insert_index; + + blocks_to_transfer(); + }; + + struct iterator_trait + { + typedef multi_type_vector parent; + typedef blocks_type blocks; + typedef typename blocks_type::iterator base_iterator; + }; + + struct reverse_iterator_trait + { + typedef multi_type_vector parent; + typedef blocks_type blocks; + typedef typename blocks_type::reverse_iterator base_iterator; + }; + + struct const_iterator_trait + { + typedef multi_type_vector parent; + typedef blocks_type blocks; + typedef typename blocks_type::const_iterator base_iterator; + }; + + struct const_reverse_iterator_trait + { + typedef multi_type_vector parent; + typedef blocks_type blocks; + typedef typename blocks_type::const_reverse_iterator base_iterator; + }; + + typedef mdds::detail::mtv::iterator_value_node itr_node; + typedef mdds::detail::mtv::private_data_forward_update itr_forward_update; + typedef mdds::detail::mtv::private_data_no_update itr_no_update; + +public: + typedef detail::iterator_base iterator; + typedef detail::iterator_base reverse_iterator; + + typedef detail::const_iterator_base const_iterator; + typedef detail::const_iterator_base + const_reverse_iterator; + + /** + * value_type is the type of a block stored in the primary array. It + * consists of the following data members: + * + *
    + *
  • type which indicates the block type,
  • + *
  • position which stores the logical position of the + * first element of the block,
  • + *
  • size which stores the logical size of the block, + * and
  • + *
  • data which stores the pointer to a secondary array + * (a.k.a. element block) which stores the actual element values, or + * nullptr in case the block represents an empty segment.
  • + *
+ */ + typedef itr_node value_type; + + typedef std::pair position_type; + typedef std::pair const_position_type; + + /** + * Move the position object to the next logical position. Caller must + * ensure the the position object is valid. + * + * @param pos position object. + * + * @return position object that points to the next logical position. + */ + static position_type next_position(const position_type& pos); + + /** + * Increment or decrement the position object by specified steps. Caller + * must ensure the the position object is valid. + * + * @param pos position object. + * @param steps steps to advance the position object. + * + * @return position object that points to the new logical position. + */ + static position_type advance_position(const position_type& pos, int steps); + + /** + * Move the position object to the next logical position. Caller must + * ensure the the position object is valid. + * + * @param pos position object. + * + * @return position object that points to the next logical position. + */ + static const_position_type next_position(const const_position_type& pos); + + /** + * Increment or decrement the position object by specified steps. Caller + * must ensure the the position object is valid. + * + * @param pos position object. + * @param steps steps to advance the position object. + * + * @return position object that points to the new logical position. + */ + static const_position_type advance_position(const const_position_type& pos, int steps); + + /** + * Extract the logical position from a position object. + * + * @param pos position object. + * + * @return logical position of the element that the position object + * references. + */ + static size_type logical_position(const const_position_type& pos); + + /** + * Get element value from a position object. The caller must specify the + * type of block in which the element is expected to be stored. + * + * @param pos position object. + * + * @return element value. + */ + template + static typename Blk::value_type get(const const_position_type& pos); + + iterator begin(); + iterator end(); + + const_iterator begin() const; + const_iterator end() const; + + const_iterator cbegin() const; + const_iterator cend() const; + + reverse_iterator rbegin(); + reverse_iterator rend(); + + const_reverse_iterator rbegin() const; + const_reverse_iterator rend() const; + + const_reverse_iterator crbegin() const; + const_reverse_iterator crend() const; + + event_func& event_handler(); + const event_func& event_handler() const; + + /** + * Default constructor. It initializes the container with empty size. + */ + multi_type_vector(); + + /** + * Constructor that takes an lvalue reference to an event handler object. + * The event handler instance will be copy-constructed. + * + * @param hdl lvalue reference to an event handler object. + */ + multi_type_vector(const event_func& hdl); + + /** + * Constructor that takes an rvalue reference to an event handler object. + * The event handler instance will be move-constructed. + * + * @param hdl rvalue reference to an event handler object. + */ + multi_type_vector(event_func&& hdl); + + /** + * Constructor that takes initial size of the container. When the size + * specified is greater than 0, it initializes the container with empty + * elements. + * + * @param init_size initial container size. + */ + multi_type_vector(size_type init_size); + + /** + * Constructor that takes initial size of the container and an element + * value to initialize the elements to. When the size specified is greater + * than 0, it initializes the container with elements that are copies of + * the value specified. + * + * @param init_size initial container size. + * @param value initial element value. + */ + template + multi_type_vector(size_type init_size, const T& value); + + /** + * Constructor that takes initial size of the container and begin and end + * iterator positions that specify a series of elements to initialize the + * container to. The container will contain copies of the elements + * specified after this call returns. + * + * @param init_size initial container size. + * @param it_begin iterator that points to the begin position of the + * values the container is being initialized to. + * @param it_end iterator that points to the end position of the values + * the container is being initialized to. The end position + * is not inclusive. + */ + template + multi_type_vector(size_type init_size, const T& it_begin, const T& it_end); + + /** + * Copy constructor. + * + * @param other the other instance to copy values from. + */ + multi_type_vector(const multi_type_vector& other); + + /** + * Move constructor. + * + * @param other the other instance to move values from. + */ + multi_type_vector(multi_type_vector&& other); + + /** + * Destructor. It deletes all allocated data blocks. + */ + ~multi_type_vector(); + + /** + * Set a value of an arbitrary type to a specified position. The type of + * the value is inferred from the value passed to this method. The new + * value will overwrite an existing value at the specified position + * position if any. + * + *

The method will throw an std::out_of_range exception + * if the specified position is outside the current container range.

+ * + *

Calling this method will not change the size of the container.

+ * + * @param pos position to insert the value to. + * @param value value to insert. + * @return iterator position pointing to the block where the value is + * inserted. + */ + template + iterator set(size_type pos, const T& value); + + /** + * Set a value of an arbitrary type to a specified position. The type of + * the value is inferred from the value passed to this method. The new + * value will overwrite an existing value at the specified position + * position if any. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the + * right block to insert the value into. The other variant that doesn't + * take an iterator always starts the block lookup from the first block, + * which does not scale well as the block size grows.

+ * + *

This position hint iterator must precede the insertion + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception + * if the specified position is outside the current container range.

+ * + *

Calling this method will not change the size of the container.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right block + * to insert the value into. + * @param pos position to insert the value to. + * @param value value to insert. + * @return iterator position pointing to the block where the value is + * inserted. + */ + template + iterator set(const iterator& pos_hint, size_type pos, const T& value); + + /** + * Set multiple values of identical type to a range of elements starting + * at specified position. Any existing values will be overwritten by the + * new values. + * + *

The method will throw an std::out_of_range exception if + * the range of new values would fall outside the current container + * range.

+ * + *

Calling this method will not change the size of the container.

+ * + * @param pos position of the first value of the series of new values + * being inserted. + * @param it_begin iterator that points to the begin position of the + * values being set. + * @param it_end iterator that points to the end position of the values + * being set. + * @return iterator position pointing to the block where the value is + * inserted. When no value insertion occurs because the value set + * is empty, the end iterator position is returned. + */ + template + iterator set(size_type pos, const T& it_begin, const T& it_end); + + /** + * Set multiple values of identical type to a range of elements starting + * at specified position. Any existing values will be overwritten by the + * new values. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the first + * insertion block. The other variant that doesn't take an iterator + * always starts the block lookup from the first block, which does not + * scale well as the block size grows.

+ * + *

This position hint iterator must precede the insertion + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception if + * the range of new values would fall outside the current container + * range.

+ * + *

Calling this method will not change the size of the container.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right block + * to insert the value into. + * @param pos position of the first value of the series of new values + * being inserted. + * @param it_begin iterator that points to the begin position of the + * values being set. + * @param it_end iterator that points to the end position of the values + * being set. + * @return iterator position pointing to the block where the value is + * inserted. When no value insertion occurs because the value set + * is empty, the end iterator position is returned. + */ + template + iterator set(const iterator& pos_hint, size_type pos, const T& it_begin, const T& it_end); + + /** + * Append a new value to the end of the container. + * + * @param value new value to be appended to the end of the container. + * + * @return iterator position pointing to the block where the value is + * appended, which in this case is always the last block of the + * container. + */ + template + iterator push_back(const T& value); + + /** + * Append a new empty element to the end of the container. + * + * @return iterator position pointing to the block where the new empty + * element is appended, which in this case is always the last + * block of the container. + */ + iterator push_back_empty(); + + /** + * Insert multiple values of identical type to a specified position. + * Existing values that occur at or below the specified position will get + * shifted after the insertion. No existing values will be overwritten by + * the inserted values. + * + *

The method will throw an std::out_of_range exception + * if the specified position is outside the current container range.

+ * + *

Calling this method will increase the size of the container by + * the length of the new values inserted.

+ * + * @param pos position at which the new values are to be inserted. + * @param it_begin iterator that points to the begin position of the + * values being inserted. + * @param it_end iterator that points to the end position of the values + * being inserted. + * @return iterator position pointing to the block where the value is + * inserted. When no value insertion occurs because the value set + * is empty, the end iterator position is returned. + */ + template + iterator insert(size_type pos, const T& it_begin, const T& it_end); + + /** + * Insert multiple values of identical type to a specified position. + * Existing values that occur at or below the specified position will get + * shifted after the insertion. No existing values will be overwritten by + * the inserted values. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the first + * insertion block. The other variant that doesn't take an iterator + * always starts the block lookup from the first block, which does not + * scale well as the block size grows.

+ * + *

This position hint iterator must precede the insertion + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception + * if the specified position is outside the current container range.

+ * + *

Calling this method will increase the size of the container by + * the length of the new values inserted.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right block + * to insert the value into. + * @param pos position at which the new values are to be inserted. + * @param it_begin iterator that points to the begin position of the + * values being inserted. + * @param it_end iterator that points to the end position of the values + * being inserted. + * @return iterator position pointing to the block where the value is + * inserted. When no value insertion occurs because the value set + * is empty, the end iterator position is returned. + */ + template + iterator insert(const iterator& pos_hint, size_type pos, const T& it_begin, const T& it_end); + + /** + * Get the value of an element at specified position. The caller must + * pass a variable of the correct type to store the value. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element value to retrieve. + * @param value (out) variable to store the retrieved value. + */ + template + void get(size_type pos, T& value) const; + + /** + * Get the value of an element at specified position. The caller must + * specify the type of the element as the template parameter e.g. + * get(1). + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element value to retrieve. + * @return element value. + */ + template + T get(size_type pos) const; + + /** + * Return the value of an element at specified position and set that + * position empty. If the element resides in a managed element block, + * this call will release that element from that block. If the element is + * on a non-managed element block, this call is equivalent to + * set_empty(pos, pos). + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element to release. + * + * @return element value. + */ + template + T release(size_type pos); + + /** + * Retrieve the value of an element at specified position and set that + * position empty. If the element resides in a managed element block, + * this call will release that element from that block. If the element is + * on a non-managed element block, this call is equivalent to + * set_empty(pos, pos). + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element to release. + * @param value element value. + * + * @return iterator referencing the block where the position of the + * released element is. + */ + template + iterator release(size_type pos, T& value); + + /** + * Retrieve the value of an element at specified position and set that + * position empty. If the element resides in a managed element block, + * this call will release that element from that block. If the element is + * on a non-managed element block, this call is equivalent to + * set_empty(pos, pos). + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the block where + * the element resides. + * @param pos position of the element to release. + * @param value element value. + * + * @return iterator referencing the block where the position of the + * released element is. + */ + template + iterator release(const iterator& pos_hint, size_type pos, T& value); + + /** + * Release all its elements, and empties its content. Calling this method + * relinquishes the ownership of all elements stored in managed element + * blocks if any. + * + *

This call is equivalent of clear() if the container consists of no + * managed element blocks.

+ */ + void release(); + + /** + * Make all elements within specified range empty, and relinquish the + * ownership of the elements in that range. All elements in the managed + * element blocks within the range will be released and the container will + * no longer manage their life cycles after the call. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container size.

+ * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @return iterator position pointing to the block where the elements are + * released. + */ + iterator release_range(size_type start_pos, size_type end_pos); + + /** + * Make all elements within specified range empty, and relinquish the + * ownership of the elements in that range. All elements in the managed + * element blocks within the range will be released and the container will + * no longer manage their life cycles after the call. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the first block + * to empty. The other variant that doesn't take an iterator always + * starts the block lookup from the first block, which does not + * scale well as the block size grows.

+ * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container size.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right + * blocks in which elements are to be released. + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @return iterator position pointing to the block where the elements are + * released. + */ + iterator release_range(const iterator& pos_hint, size_type start_pos, size_type end_pos); + + /** + * Given the logical position of an element, get the iterator of the block + * where the element is located, and its offset from the first element of + * that block. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range, except + * when the specified position is the position immediately after the last + * valid position, it will return a valid position object representing + * the end position.

+ * + * @param pos logical position of the element. + * @return position object that stores an iterator referencing the element + * block where the element resides, and its offset within that + * block. + */ + position_type position(size_type pos); + + /** + * Given the logical position of an element, get the iterator of the block + * where the element is located, and its offset from the first element of + * that block. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range, except + * when the specified position is the position immediately after the last + * valid position, it will return a valid position object representing + * the end position.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the element + * position. + * @param pos logical position of the element. + * @return position object that stores an iterator referencing the element + * block where the element resides, and its offset within that + * block. + */ + position_type position(const iterator& pos_hint, size_type pos); + + /** + * Given the logical position of an element, get an iterator referencing + * the element block where the element is located, and its offset from the + * first element of that block. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element. + * @return position object that stores an iterator referencing the element + * block where the element resides, and its offset within that + * block. + */ + const_position_type position(size_type pos) const; + + /** + * Given the logical position of an element, get an iterator referencing + * the element block where the element is located, and its offset from the + * first element of that block. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the element + * position. + * @param pos logical position of the element. + * @return position object that stores an iterator referencing the element + * block where the element resides, and its offset within the + * block. + */ + const_position_type position(const const_iterator& pos_hint, size_type pos) const; + + /** + * Move elements from one container to another. After the move, the + * segment where the elements were in the source container becomes empty. + * When transferring managed elements, this call transfers ownership of + * the moved elements to the destination container. The moved elements + * will overwrite any existing elements in the destination range of the + * receiving container. Transfer of elements within the same container is + * not allowed. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is greater than or equal to + * the source container size, or the destination container is not + * large enough to accommodate the transferred elements.

+ * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @param dest destination container to which the elements are to be + * moved. + * @param dest_pos position in the destination container to which the + * elements are to be moved. + * + * @return iterator referencing the block where the moved elements were + * prior to the transfer. + */ + iterator transfer(size_type start_pos, size_type end_pos, multi_type_vector& dest, size_type dest_pos); + + /** + * Move elements from one container to another. After the move, the + * segment where the elements were in the source container becomes empty. + * When transferring managed elements, this call transfers ownership of + * the moved elements to the new container. The moved elements will + * overwrite any existing elements in the destination range of the + * receiving container. Transfer of elements within the same container is + * not allowed. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is greater than or equal to + * the source container size, or the destination container is not large + * enough to accommodate the transferred elements.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the blocks + * where the elements to be transferred reside. + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @param dest destination container to which the elements are to be + * moved. + * @param dest_pos position in the destination container to which the + * elements are to be moved. + * + * @return iterator referencing the block where the moved elements were + * prior to the transfer. + */ + iterator transfer( + const iterator& pos_hint, size_type start_pos, size_type end_pos, multi_type_vector& dest, size_type dest_pos); + + /** + * Get the type of an element at specified position. + * + * @param pos position of the element. + * + * @return element type. + */ + mtv::element_t get_type(size_type pos) const; + + /** + * Check if element at specified position is empty of not. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container + * range.

+ * + * @param pos position of the element to check. + * + * @return true if the element is empty, false otherwise. + */ + bool is_empty(size_type pos) const; + + /** + * Set specified range of elements to be empty. Any existing values will + * be overwritten. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container size.

+ * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @return iterator position pointing to the block where the elements are + * emptied. + */ + iterator set_empty(size_type start_pos, size_type end_pos); + + /** + * Set specified range of elements to be empty. Any existing values will + * be overwritten. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the first block + * to empty. The other variant that doesn't take an iterator always + * starts the block lookup from the first block, which does not + * scale well as the block size grows.

+ * + *

This position hint iterator must precede the start + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container size.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right + * blocks to empty. + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @return iterator position pointing to the block where the elements are + * emptied. + */ + iterator set_empty(const iterator& pos_hint, size_type start_pos, size_type end_pos); + + /** + * Erase elements located between specified start and end positions. The + * end positions are both inclusive. Those elements originally located + * after the specified end position will get shifted up after the erasure. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container range.

+ * + *

Calling this method will decrease the size of the container by + * the length of the erased range.

+ * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + */ + void erase(size_type start_pos, size_type end_pos); + + /** + * Insert a range of empty elements at specified position. Those elements + * originally located after the insertion position will get shifted down + * after the insertion. + * + *

The method will throw an std::out_of_range exception if + * either the specified position is outside the current container + * range.

+ * + *

Calling this method will increase the size of the container by + * the length of the inserted empty elements.

+ * + * @param pos position at which to insert a range of empty elements. + * @param length number of empty elements to insert. + * @return iterator position pointing to the block where the empty range + * is inserted. When no insertion occurs because the length is + * zero, the end iterator position is returned. + */ + iterator insert_empty(size_type pos, size_type length); + + /** + * Insert a range of empty elements at specified position. Those elements + * originally located after the insertion position will get shifted down + * after the insertion. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the block in + * which to insert the new empty segment. The other variant that doesn't + * take an iterator always starts the block lookup from the first block, + * which does not scale well as the block size grows.

+ * + *

This position hint iterator must precede the start + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception if + * either the specified position is outside the current container + * range.

+ * + *

Calling this method will increase the size of the container by + * the length of the inserted empty elements.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right block + * in which to insert the empty segment. + * @param pos position at which to insert a range of empty elements. + * @param length number of empty elements to insert. + * @return iterator position pointing to the block where the empty range + * is inserted. When no insertion occurs because the length is + * zero, the end iterator position is returned. + */ + iterator insert_empty(const iterator& pos_hint, size_type pos, size_type length); + + /** + * Clear the content of the container. The size of the container will + * become zero after calling this method. + */ + void clear(); + + /** + * Return the current container size. + * + * @return current container size. + */ + size_type size() const; + + /** + * Return the current number of blocks in the primary array. Each + * non-empty block stores a secondary block that stores elements in a + * contiguous region in memory (element block) and the number of elements + * it stores. An empty block only stores its logical size and does not + * store an actual element block. + * + *

For instance, if the container stores values of double-precision + * type at rows 0 to 2, values of std::string type at 3 to 7, and empty + * values at 8 to 10, it would consist of three blocks: one that stores + * double values, one that stores std::string values, and one that + * represents the empty value range in this exact order. In this specific + * scenario, block_size() returns 3, and size() + * returns 11.

+ * + * @return current number of blocks in the primary array. + */ + size_type block_size() const; + + /** + * Return whether or not the container is empty. + * + * @return true if the container is empty, false otherwise. + */ + bool empty() const; + + /** + * Extend or shrink the container. When extending the container, it + * appends a series of empty elements to the end. When shrinking, the + * elements at the end of the container get stripped off. + * + * @param new_size size of the container after the resize. + */ + void resize(size_type new_size); + + /** + * Swap the content with another container. + * + * @param other another container to swap content with. + */ + void swap(multi_type_vector& other); + + /** + * Swap a part of the content with another instance. + * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @param other another instance to swap the content with. + * @param other_pos insertion position in the other instance. + */ + void swap(size_type start_pos, size_type end_pos, multi_type_vector& other, size_type other_pos); + + /** + * Trim excess capacity from all non-empty blocks. + */ + void shrink_to_fit(); + + bool operator==(const multi_type_vector& other) const; + bool operator!=(const multi_type_vector& other) const; + + multi_type_vector& operator=(const multi_type_vector& other); + multi_type_vector& operator=(multi_type_vector&& other); + + /** + * Return the numerical identifier that represents passed element. + * + * @param elem element value. + * + * @return numerical identifier representing the element. + */ + template + static mtv::element_t get_element_type(const T& elem); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + void dump_blocks(std::ostream& os) const; + + void check_block_integrity() const; +#endif + +private: + /** + * Delete only the element block owned by an outer block. + * + * @param blk reference to the instance of an outer block that may own an + * element block instance. + */ + void delete_element_block(block& blk); + + /** + * Delete the element block(s) owned by one or more outer blocks in the + * specified iterator ranges. + * + * @param it start position. + * @param it_end end position (not inclusive). + */ + void delete_element_blocks(typename blocks_type::iterator it, typename blocks_type::iterator it_end); + + template + iterator set_impl(size_type pos, size_type block_index, const T& value); + + template + iterator release_impl(size_type pos, size_type block_index, T& value); + + template + iterator push_back_impl(const T& value); + + /** + * Find the correct block position for a given logical row ID. + * + * @param row logical position of an element. + * @param start_block_index index of the first block to start the search + * from. + * + * @return index of the block that contains the specified logical row ID. + */ + size_type get_block_position(size_type row, size_type start_block_index = 0) const; + + /** + * Same as above, but try to infer block position from the private position + * data stored in the iterator first before trying full search. + */ + size_type get_block_position(const typename value_type::private_data& pos_data, size_type row) const; + + void resize_impl(size_type new_size); + + template + void create_new_block_with_new_cell(element_block_type*& data, const T& cell); + + template + iterator set_cell_to_middle_of_block(size_type block_index, size_type pos_in_block, const T& cell); + + template + void append_cell_to_block(size_type block_index, const T& cell); + + template + iterator set_cell_to_empty_block(size_type block_index, size_type pos_in_block, const T& cell); + + template + iterator set_cell_to_block_of_size_one(size_type block_index, const T& cell); + + template + void set_cell_to_top_of_data_block(size_type block_index, const T& cell); + + template + void set_cell_to_bottom_of_data_block(size_type block_index, const T& cell); + + iterator transfer_impl( + size_type start_pos, size_type end_pos, size_type block_index1, multi_type_vector& dest, size_type dest_pos); + + /** + * All elements to transfer to the other container is in the same block. + */ + iterator transfer_single_block( + size_type start_pos, size_type end_pos, size_type block_index1, multi_type_vector& dest, size_type dest_pos); + + /** + * Elements to transfer to the other container span across multiple + * blocks. + */ + iterator transfer_multi_blocks( + size_type start_pos, size_type end_pos, size_type block_index1, size_type block_index2, multi_type_vector& dest, + size_type dest_pos); + + /** + * @param start_pos logical start position. + * @param end_pos logical end position. + * @param block_index1 index of the first block + * @param overwrite when true, and when the stored values are pointers to + * heap objects, objects pointed to by the overwritten + * pointers should be freed from memory. + */ + iterator set_empty_impl(size_type start_pos, size_type end_pos, size_type block_index1, bool overwrite); + + void swap_impl( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index1, + size_type block_index2, size_type dblock_index1, size_type dblock_index2); + + void swap_single_block( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index, + size_type other_block_index); + + void swap_single_to_multi_blocks( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index, + size_type dst_block_index1, size_type dst_block_index2); + + void swap_multi_to_multi_blocks( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index1, + size_type block_index2, size_type dblock_index1, size_type dblock_index2); + + void insert_blocks_at(size_type position, size_type insert_pos, blocks_type& new_blocks); + + void prepare_blocks_to_transfer( + blocks_to_transfer& bucket, size_type block_index1, size_type offset1, size_type block_index2, + size_type offset2); + + iterator set_whole_block_empty(size_type block_index, bool overwrite); + + iterator set_empty_in_single_block(size_type start_row, size_type end_row, size_type block_index, bool overwrite); + + /** + * @param start_row logical start position. + * @param end_row logical end position. + * @param block_index1 index of the first block. + * @param block_index2 index of the last block. + * @param overwrite when true, and when the stored values are pointers to + * heap objects, objects pointed to by the overwritten + * pointers should be freed from memory. + */ + iterator set_empty_in_multi_blocks( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, bool overwrite); + + void erase_impl(size_type start_pos, size_type end_pos); + void erase_in_single_block(size_type start_pos, size_type end_pos, size_type block_pos); + + /** + * @param pos logical position at which to insert an empty segment. + * @param block_index index of the block. + * @param length length of the emtpy segment to insert. + */ + iterator insert_empty_impl(size_type pos, size_type block_index, size_type length); + + template + iterator set_cells_impl( + size_type row, size_type end_row, size_type block_index1, const T& it_begin, const T& it_end); + + template + iterator insert_cells_impl(size_type row, size_type block_index, const T& it_begin, const T& it_end); + + template + iterator set_cells_to_single_block( + size_type start_row, size_type end_row, size_type block_index, const T& it_begin, const T& it_end); + + template + iterator set_cells_to_multi_blocks( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end); + + template + iterator set_cells_to_multi_blocks_block1_non_equal( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end); + + template + iterator set_cells_to_multi_blocks_block1_non_empty( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end); + + /** + * Merge with previous or next block as needed. + * + * @param block_index index of the block that may need merging. + * + * @return size of previous block if the block is merged with the previous + * block, or 0 if it didn't merge with the previous block. + */ + size_type merge_with_adjacent_blocks(size_type block_index); + + /** + * Merge only with the next block if the two are of the same type. + * + * @param block_index index of the block that may need merging. + * + * @return true if merge occurs, false otherwise. + */ + bool merge_with_next_block(size_type block_index); + + template + bool append_to_prev_block( + size_type block_index, element_category_type cat, size_type length, const T& it_begin, const T& it_end); + + template + void insert_cells_to_middle(size_type row, size_type block_index, const T& it_begin, const T& it_end); + + /** + * Set a new block in the middle of an existing block. This call inserts + * two new blocks below the specificed block position. The first one will + * be empty, and the second one will contain the lower elements of the + * existing block. + * + * @param block_index index of block into which to set a new block. + * @param offset position in the existing block to set the new block to. + * @param new_block_size size of the new block + * @param overwrite whether or not to overwrite the elements replaced by + * the new block. + * @return reference to the middle block + */ + block& set_new_block_to_middle(size_type block_index, size_type offset, size_type new_block_size, bool overwrite); + + block* get_previous_block_of_type(size_type block_index, element_category_type cat); + + /** + * @param block_index index of the current block. + * @param cat desired block type. + * + * @return pointer to the next block if the next block exists and it's of + * specified type, otherwise nullptr will be returned. + */ + block* get_next_block_of_type(size_type block_index, element_category_type cat); + + /** + * Send elements from a source block to place them in a destination block. + * In return, the method returns the elements in the destination block + * that have been replaced by the elements sent by the caller. The caller + * needs to manage the life cycle of the returned block. + * + * @param src_data source data block from which the elements are sent. + * @param src_offset position of the first element in the source block. + * @param dst_index destination block index. + * @param dst_offset position in the destination block where the sent + * elements are to be placed. + * @param len length of elements. + * + * @return heap allocated block that contains the overwritten elements + * originally in the destination block. The caller needs to manage + * its life cycle. + */ + element_block_type* exchange_elements( + const element_block_type& src_data, size_type src_offset, size_type dst_index, size_type dst_offset, + size_type len); + + void exchange_elements( + const element_block_type& src_data, size_type src_offset, size_type dst_index1, size_type dst_offset1, + size_type dst_index2, size_type dst_offset2, size_type len, blocks_type& new_blocks); + + bool append_empty(size_type len); + + inline iterator get_iterator(size_type block_index) + { + typename blocks_type::iterator block_pos = m_blocks.begin(); + std::advance(block_pos, block_index); + return iterator(block_pos, m_blocks.end(), this, block_index); + } + + inline const_iterator get_const_iterator(size_type block_index) const + { + typename blocks_type::const_iterator block_pos = m_blocks.begin(); + std::advance(block_pos, block_index); + return const_iterator(block_pos, m_blocks.end(), this, block_index); + } + +private: + using adjust_block_positions_func = detail::adjust_block_positions; + + event_func m_hdl_event; + blocks_type m_blocks; + size_type m_cur_size; +}; + +}}} // namespace mdds::mtv::aos + +#include "main_def.inl" + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/aos/main_def.inl b/include/mdds/multi_type_vector/aos/main_def.inl new file mode 100644 index 0000000..9e1140e --- /dev/null +++ b/include/mdds/multi_type_vector/aos/main_def.inl @@ -0,0 +1,4837 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#include "../env.hpp" +#include "../util.hpp" +#if MDDS_MTV_USE_STANDARD_ELEMENT_BLOCKS +#include "../standard_element_blocks.hpp" +#endif + +#include +#include + +namespace mdds { namespace mtv { namespace aos { + +namespace detail { + +template +inline SizeT calc_next_block_position(const std::vector& blocks, SizeT block_index) +{ + assert(block_index < blocks.size()); + const Blk& blk = blocks[block_index]; + return blk.position + blk.size; +} + +template +inline auto calc_next_block_position(const Blk& blk) -> decltype(blk.position) +{ + return blk.position + blk.size; +} + +template +bool compare_blocks(const Blk& left, const Blk& right) +{ + return left.position < right.position; +} + +} // namespace detail + +template +multi_type_vector::block::block() : position(0), size(0), data(nullptr) +{} + +template +multi_type_vector::block::block(size_type _position, size_type _size) + : position(_position), size(_size), data(nullptr) +{} + +template +multi_type_vector::block::block(size_type _position, size_type _size, element_block_type* _data) + : position(_position), size(_size), data(_data) +{} + +template +void multi_type_vector::block::swap(block& other) +{ + std::swap(position, other.position); + std::swap(size, other.size); + std::swap(data, other.data); +} + +template +void multi_type_vector::block::clone_to(block& other) const +{ + other.position = position; + other.size = size; + if (data) + other.data = block_funcs::clone_block(*data); + else + other.data = nullptr; +} + +template +multi_type_vector::blocks_to_transfer::blocks_to_transfer() : insert_index(0) +{} + +template +typename multi_type_vector::position_type multi_type_vector::next_position(const position_type& pos) +{ + position_type ret = pos; + if (pos.second + 1 < pos.first->size) + { + // Next element is still in the same block. + ++ret.second; + } + else + { + ++ret.first; + ret.second = 0; + } + + return ret; +} + +template +typename multi_type_vector::position_type multi_type_vector::advance_position( + const position_type& pos, int steps) +{ + return mdds::mtv::detail::advance_position(pos, steps); +} + +template +typename multi_type_vector::const_position_type multi_type_vector::next_position( + const const_position_type& pos) +{ + const_position_type ret = pos; + if (pos.second + 1 < pos.first->size) + { + // Next element is still in the same block. + ++ret.second; + } + else + { + ++ret.first; + ret.second = 0; + } + + return ret; +} + +template +typename multi_type_vector::const_position_type multi_type_vector::advance_position( + const const_position_type& pos, int steps) +{ + return mdds::mtv::detail::advance_position(pos, steps); +} + +template +typename multi_type_vector::size_type multi_type_vector::logical_position( + const const_position_type& pos) +{ + return pos.first->position + pos.second; +} + +template +template +typename Blk::value_type multi_type_vector::get(const const_position_type& pos) +{ + return mdds::mtv::detail::get_block_element_at(*pos.first->data, pos.second); +} + +template +typename multi_type_vector::iterator multi_type_vector::begin() +{ + return iterator(m_blocks.begin(), m_blocks.end(), this, 0); +} + +template +typename multi_type_vector::iterator multi_type_vector::end() +{ + return iterator(m_blocks.end(), m_blocks.end(), this, m_blocks.size()); +} + +template +typename multi_type_vector::const_iterator multi_type_vector::begin() const +{ + return cbegin(); +} + +template +typename multi_type_vector::const_iterator multi_type_vector::end() const +{ + return cend(); +} + +template +typename multi_type_vector::const_iterator multi_type_vector::cbegin() const +{ + return const_iterator(m_blocks.cbegin(), m_blocks.cend(), this, 0); +} + +template +typename multi_type_vector::const_iterator multi_type_vector::cend() const +{ + return const_iterator(m_blocks.end(), m_blocks.end(), this, m_blocks.size()); +} + +template +typename multi_type_vector::reverse_iterator multi_type_vector::rbegin() +{ + return reverse_iterator(m_blocks.rbegin(), m_blocks.rend(), this, 0); +} + +template +typename multi_type_vector::reverse_iterator multi_type_vector::rend() +{ + return reverse_iterator(m_blocks.rend(), m_blocks.rend(), this, 0); +} + +template +typename multi_type_vector::const_reverse_iterator multi_type_vector::rbegin() const +{ + return const_reverse_iterator(m_blocks.rbegin(), m_blocks.rend(), this, 0); +} + +template +typename multi_type_vector::const_reverse_iterator multi_type_vector::rend() const +{ + return const_reverse_iterator(m_blocks.rend(), m_blocks.rend(), this, 0); +} + +template +typename multi_type_vector::const_reverse_iterator multi_type_vector::crbegin() const +{ + return const_reverse_iterator(m_blocks.crbegin(), m_blocks.crend(), this, 0); +} + +template +typename multi_type_vector::const_reverse_iterator multi_type_vector::crend() const +{ + return const_reverse_iterator(m_blocks.crend(), m_blocks.crend(), this, 0); +} + +template +typename multi_type_vector::event_func& multi_type_vector::event_handler() +{ + return m_hdl_event; +} + +template +const typename multi_type_vector::event_func& multi_type_vector::event_handler() const +{ + return m_hdl_event; +} + +template +multi_type_vector::multi_type_vector() : m_cur_size(0) +{} + +template +multi_type_vector::multi_type_vector(const event_func& hdl) : m_hdl_event(hdl), m_cur_size(0) +{} + +template +multi_type_vector::multi_type_vector(event_func&& hdl) : m_hdl_event(std::move(hdl)), m_cur_size(0) +{} + +template +multi_type_vector::multi_type_vector(size_type init_size) : m_cur_size(init_size) +{ + if (!init_size) + return; + + // Initialize with an empty block that spans from 0 to max. + m_blocks.emplace_back(0, init_size); +} + +template +template +multi_type_vector::multi_type_vector(size_type init_size, const T& value) : m_cur_size(init_size) +{ + if (!init_size) + return; + + element_block_type* data = mdds_mtv_create_new_block(init_size, value); + m_hdl_event.element_block_acquired(data); + m_blocks.emplace_back(0, init_size, data); +} + +template +template +multi_type_vector::multi_type_vector(size_type init_size, const T& it_begin, const T& it_end) + : m_cur_size(init_size) +{ + if (!m_cur_size) + return; + + size_type data_len = std::distance(it_begin, it_end); + if (m_cur_size != data_len) + throw invalid_arg_error("Specified size does not match the size of the initial data array."); + + element_block_type* data = mdds_mtv_create_new_block(*it_begin, it_begin, it_end); + m_hdl_event.element_block_acquired(data); + m_blocks.emplace_back(0, m_cur_size, data); +} + +template +multi_type_vector::multi_type_vector(const multi_type_vector& other) + : m_hdl_event(other.m_hdl_event), m_cur_size(other.m_cur_size) +{ + // Clone all the blocks. + m_blocks.reserve(other.m_blocks.size()); + typename blocks_type::const_iterator it = other.m_blocks.begin(), it_end = other.m_blocks.end(); + block tmp; + for (; it != it_end; ++it) + { + it->clone_to(tmp); + m_blocks.emplace_back(tmp.position, tmp.size, tmp.data); + if (tmp.data) + m_hdl_event.element_block_acquired(tmp.data); + } + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in copy construction" << std::endl; + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +multi_type_vector::multi_type_vector(multi_type_vector&& other) + : m_hdl_event(std::move(other.m_hdl_event)), m_blocks(std::move(other.m_blocks)), m_cur_size(other.m_cur_size) +{ +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in move construction" << std::endl; + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +multi_type_vector::~multi_type_vector() +{ + delete_element_blocks(m_blocks.begin(), m_blocks.end()); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set(size_type pos, const T& value) +{ + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = set_impl(pos, block_index, value); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in set (pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set( + const iterator& pos_hint, size_type pos, const T& value) +{ + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = set_impl(pos, block_index, value); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in set (pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +void multi_type_vector::delete_element_block(block& blk) +{ + if (!blk.data) + // This block is empty. + return; + + m_hdl_event.element_block_released(blk.data); + block_funcs::delete_block(blk.data); + blk.data = nullptr; +} + +template +void multi_type_vector::delete_element_blocks( + typename blocks_type::iterator it, typename blocks_type::iterator it_end) +{ + std::for_each(it, it_end, [&](block& r) { delete_element_block(r); }); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_impl( + size_type pos, size_type block_index, const T& value) +{ + size_type start_row = m_blocks[block_index].position; + element_category_type cat = mdds_mtv_get_element_type(value); + + typename blocks_type::iterator block_pos = m_blocks.begin(); + std::advance(block_pos, block_index); + block* blk = &*block_pos; + assert(blk->size > 0); // block size should never be zero at any time. + + assert(pos >= start_row); + size_type pos_in_block = pos - start_row; + assert(pos_in_block < blk->size); + + if (!blk->data) + { + // This is an empty block. + return set_cell_to_empty_block(block_index, pos_in_block, value); + } + + assert(blk->data); + element_category_type blk_cat = mdds::mtv::get_block_type(*blk->data); + + if (blk_cat == cat) + { + // This block is of the same type as the cell being inserted. + size_type i = pos - start_row; + block_funcs::overwrite_values(*blk->data, i, 1); + mdds_mtv_set_value(*blk->data, i, value); + return iterator(block_pos, m_blocks.end(), this, block_index); + } + + assert(blk_cat != cat); + + if (pos == start_row) + { + // Insertion point is at the start of the block. + if (blk->size == 1) + { + return set_cell_to_block_of_size_one(block_index, value); + } + + assert(blk->size > 1); + block* blk_prev = get_previous_block_of_type(block_index, cat); + if (blk_prev) + { + // Append to the previous block. + blk->size -= 1; + blk->position += 1; + block_funcs::overwrite_values(*blk->data, 0, 1); + block_funcs::erase(*blk->data, 0); + blk_prev->size += 1; + mdds_mtv_append_value(*blk_prev->data, value); + return get_iterator(block_index - 1); + } + + set_cell_to_top_of_data_block(block_index, value); + return get_iterator(block_index); + } + + if (pos < (start_row + blk->size - 1)) + { + // Insertion point is somewhere in the middle of the block. + return set_cell_to_middle_of_block(block_index, pos_in_block, value); + } + + // Insertion point is at the end of the block. + assert(pos == (start_row + blk->size - 1)); + assert(pos > start_row); + assert(blk->size > 1); + + if (block_index == 0) + { + if (m_blocks.size() == 1) + { + // This is the only block. Pop the last value from the + // previous block, and insert a new block for the cell being + // inserted. + set_cell_to_bottom_of_data_block(0, value); + iterator itr = end(); + --itr; + return itr; + } + + block* blk_next = get_next_block_of_type(block_index, cat); + if (!blk_next) + { + // Pop the last cell of the current block, and insert a new block + // with the new cell. + set_cell_to_bottom_of_data_block(0, value); + iterator itr = begin(); + ++itr; + return itr; + } + + // Pop the last cell off the current block, and prepend the + // new value to the next block. + block_funcs::overwrite_values(*blk->data, blk->size - 1, 1); + block_funcs::erase(*blk->data, blk->size - 1); + blk->size -= 1; + mdds_mtv_prepend_value(*blk_next->data, value); + blk_next->size += 1; + blk_next->position -= 1; + + return get_iterator(block_index + 1); + } + + assert(block_index > 0); + + if (block_index == m_blocks.size() - 1) + { + // This is the last block. + set_cell_to_bottom_of_data_block(block_index, value); + iterator itr = end(); + --itr; + return itr; + } + + block* blk_next = get_next_block_of_type(block_index, cat); + if (!blk_next) + { + // Next block is either empty or of different type than that of the cell being inserted. + set_cell_to_bottom_of_data_block(block_index, value); // This invalidates m_blocks. + return get_iterator(block_index + 1); + } + + // Pop the last element from the current block, and prepend the cell + // into the next block. + block_funcs::overwrite_values(*blk->data, blk->size - 1, 1); + block_funcs::erase(*blk->data, blk->size - 1); + blk->size -= 1; + mdds_mtv_prepend_value(*blk_next->data, value); + blk_next->size += 1; + blk_next->position -= 1; + + return get_iterator(block_index + 1); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::release_impl( + size_type pos, size_type block_index, T& value) +{ + const block& blk = m_blocks[block_index]; + size_type start_pos = blk.position; + + if (!blk.data) + { + // Empty cell block. There is no element to release. + mdds_mtv_get_empty_value(value); + return get_iterator(block_index); + } + + assert(pos >= start_pos); + assert(blk.data); // data for non-empty blocks should never be nullptr. + size_type idx = pos - start_pos; + mdds_mtv_get_value(*blk.data, idx, value); + + // Set the element slot empty without overwriting it. + return set_empty_in_single_block(pos, pos, block_index, false); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set( + size_type pos, const T& it_begin, const T& it_end) +{ + auto res = mdds::mtv::detail::calc_input_end_position(it_begin, it_end, pos, m_cur_size); + + if (!res.second) + return end(); + + size_type end_pos = res.first; + + size_type block_index1 = get_block_position(pos); + if (block_index1 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + auto ret = set_cells_impl(pos, end_pos, block_index1, it_begin, it_end); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in set (pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set( + const iterator& pos_hint, size_type pos, const T& it_begin, const T& it_end) +{ + auto res = mdds::mtv::detail::calc_input_end_position(it_begin, it_end, pos, m_cur_size); + if (!res.second) + return end(); + + size_type end_pos = res.first; + + size_type block_index1 = get_block_position(pos_hint->__private_data, pos); + return set_cells_impl(pos, end_pos, block_index1, it_begin, it_end); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::push_back(const T& value) +{ +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + auto ret = push_back_impl(value); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in push_back" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::push_back_impl(const T& value) +{ + element_category_type cat = mdds_mtv_get_element_type(value); + + block* blk_last = m_blocks.empty() ? nullptr : &m_blocks.back(); + if (!blk_last || !blk_last->data || cat != get_block_type(*blk_last->data)) + { + // Either there is no block, or the last block is empty or of + // different type. Append a new block. + size_type block_index = m_blocks.size(); + size_type start_pos = m_cur_size; + + m_blocks.emplace_back(start_pos, 1); + create_new_block_with_new_cell(m_blocks.back().data, value); + ++m_cur_size; + + return get_iterator(block_index); + } + + assert(blk_last); + assert(blk_last->data); + assert(cat == get_block_type(*blk_last->data)); + + // Append the new value to the last block. + size_type block_index = m_blocks.size() - 1; + + mdds_mtv_append_value(*blk_last->data, value); + ++blk_last->size; + ++m_cur_size; + + return get_iterator(block_index); +} + +template +typename multi_type_vector::iterator multi_type_vector::push_back_empty() +{ + size_type block_index = m_blocks.size(); + + if (!append_empty(1)) + { + // Last empty block has been extended. + --block_index; + } + + // Get the iterator of the last block. + typename blocks_type::iterator block_pos = m_blocks.end(); + --block_pos; + + return iterator(block_pos, m_blocks.end(), this, block_index); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::insert( + size_type pos, const T& it_begin, const T& it_end) +{ + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::insert", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = insert_cells_impl(pos, block_index, it_begin, it_end); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + os << "block integrity check failed in insert (pos=" << pos + << "; value-size=" << std::distance(it_begin, it_end) << "; value-type=" << cat << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::insert( + const iterator& pos_hint, size_type pos, const T& it_begin, const T& it_end) +{ + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::insert", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = insert_cells_impl(pos, block_index, it_begin, it_end); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + os << "block integrity check failed in insert (pos=" << pos + << "; value-size=" << std::distance(it_begin, it_end) << "; value-type=" << cat << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +typename multi_type_vector::size_type multi_type_vector::get_block_position( + size_type row, size_type start_block_index) const +{ + if (row >= m_cur_size || start_block_index >= m_blocks.size()) + return m_blocks.size(); + + auto it0 = m_blocks.begin(); + std::advance(it0, start_block_index); + + block b(row, 0); + auto it = std::lower_bound(it0, m_blocks.end(), b, detail::compare_blocks); + + if (it == m_blocks.end() || it->position != row) + { + // Binary search has overshot by one block. Move back one. + assert(it != it0); + --it; + } + + assert(it->position <= row); + assert(row < it->position + it->size); + return std::distance(it0, it) + start_block_index; +} + +template +typename multi_type_vector::size_type multi_type_vector::get_block_position( + const typename value_type::private_data& pos_data, size_type row) const +{ + size_type block_index = 0; + if (pos_data.parent == this && pos_data.block_index < m_blocks.size()) + block_index = pos_data.block_index; + + size_type start_row = m_blocks[block_index].position; + if (row < start_row) + { + // Position hint is past the insertion position. + // Walk back if that seems efficient. + if (row > start_row / 2) + { + for (size_type i = block_index; i > 0;) + { + --i; + start_row = m_blocks[i].position; + if (row >= start_row) + { + // Row is in this block. + return i; + } + // Specified row is not in this block. + } + assert(start_row == 0); + } + // Otherwise reset. + block_index = 0; + } + + return get_block_position(row, block_index); +} + +template +template +void multi_type_vector::create_new_block_with_new_cell(element_block_type*& data, const T& cell) +{ + if (data) + { + m_hdl_event.element_block_released(data); + block_funcs::delete_block(data); + } + + // New cell block with size 1. + data = mdds_mtv_create_new_block(1, cell); + if (!data) + throw general_error("Failed to create new block."); + + m_hdl_event.element_block_acquired(data); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cell_to_middle_of_block( + size_type block_index, size_type pos_in_block, const T& cell) +{ + block& blk_new = set_new_block_to_middle(block_index, pos_in_block, 1, true); + create_new_block_with_new_cell(blk_new.data, cell); + + // Return the iterator referencing the inserted block. + return get_iterator(block_index + 1); +} + +template +template +void multi_type_vector::append_cell_to_block(size_type block_index, const T& cell) +{ + m_blocks[block_index].size += 1; + mdds_mtv_append_value(*m_blocks[block_index].data, cell); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cell_to_empty_block( + size_type block_index, size_type pos_in_block, const T& cell) +{ + block* blk = &m_blocks[block_index]; + assert(!blk->data); // In this call, the current block is an empty block. + + if (block_index == 0) + { + // Topmost block. + if (m_blocks.size() == 1) + { + // this is the only block. + assert(blk->size == m_cur_size); + if (m_cur_size == 1) + { + // This column is allowed to have only one row! + assert(pos_in_block == 0); + create_new_block_with_new_cell(blk->data, cell); + return begin(); + } + + // block has multiple rows. + if (pos_in_block == 0) + { + // Insert into the first cell in this block. + blk->size -= 1; + assert(blk->size > 0); + + m_blocks.emplace(m_blocks.begin(), 0, 1); + blk = &m_blocks[0]; // old pointer is invalid. + create_new_block_with_new_cell(blk->data, cell); + + m_blocks[1].position = 1; + return begin(); + } + + if (pos_in_block == blk->size - 1) + { + // Insert into the last cell in block. + blk->size -= 1; + assert(blk->size > 0); + + m_blocks.emplace_back(blk->size, 1); + blk = &m_blocks.back(); // old pointer is invalid. + + create_new_block_with_new_cell(blk->data, cell); + iterator ret = end(); + --ret; + return ret; + } + + // Insert into the middle of the block. + return set_cell_to_middle_of_block(block_index, pos_in_block, cell); + } + + // This topmost empty block is followed by a non-empty block. + + if (pos_in_block == 0) + { + assert(block_index < m_blocks.size() - 1); + if (blk->size == 1) + { + // Top empty block with only one cell size. + element_category_type cat = mdds_mtv_get_element_type(cell); + block* blk_next = get_next_block_of_type(block_index, cat); + if (blk_next) + { + // Remove this one-cell empty block from the top, and + // prepend the cell to the next block. + delete_element_block(m_blocks.front()); + m_blocks.erase(m_blocks.begin()); + blk = &m_blocks.front(); // old pointer is invalid. + blk->size += 1; + blk->position -= 1; + mdds_mtv_prepend_value(*blk->data, cell); + } + else + { + create_new_block_with_new_cell(blk->data, cell); + } + } + else + { + // Shrink this topmost block by one and set the new value above it. + assert(blk->size > 1); + blk->size -= 1; + blk->position = 1; + m_blocks.emplace(m_blocks.begin(), 0, 1); + create_new_block_with_new_cell(m_blocks.front().data, cell); + } + + return begin(); + } + + if (pos_in_block == blk->size - 1) + { + // Immediately above a non-empty block. + element_category_type cat = mdds_mtv_get_element_type(cell); + block* blk_next = get_next_block_of_type(block_index, cat); + if (blk_next) + { + assert(blk->size > 1); + // Shrink this empty block by one, and prepend the cell to the next block. + blk->size -= 1; + blk_next->size += 1; + blk_next->position -= 1; + mdds_mtv_prepend_value(*blk_next->data, cell); + } + else + { + // Shrink the current empty block by one, and create a new block of size 1 to store the new value. + blk->size -= 1; + typename blocks_type::iterator it = m_blocks.begin(); + std::advance(it, block_index + 1); + size_type new_position = detail::calc_next_block_position(*blk); + m_blocks.emplace(it, new_position, 1); + blk = &m_blocks[block_index]; // old pointer is invalid. + block* blk2 = &m_blocks[block_index + 1]; + create_new_block_with_new_cell(blk2->data, cell); + } + + return get_iterator(block_index + 1); + } + + // Inserting into the middle of an empty block. + return set_cell_to_middle_of_block(block_index, pos_in_block, cell); + } + + // This empty block is right below a non-empty block. + assert(block_index > 0 && m_blocks[block_index - 1].data != nullptr); + + if (pos_in_block == 0) + { + // New cell is right below the non-empty block. + element_category_type blk_cat_prev = mdds::mtv::get_block_type(*m_blocks[block_index - 1].data); + element_category_type cat = mdds_mtv_get_element_type(cell); + if (blk_cat_prev == cat) + { + // Extend the previous block by one to insert this cell. + + if (blk->size == 1) + { + // Check if we need to merge with the following block. + if (block_index == m_blocks.size() - 1) + { + // Last block. Delete this block and extend the previous + // block by one. + delete_element_block(m_blocks[block_index]); + m_blocks.pop_back(); + append_cell_to_block(block_index - 1, cell); + } + else + { + // Block exists below. + block* blk_next = get_next_block_of_type(block_index, blk_cat_prev); + if (blk_next) + { + assert(blk_next->data); // Empty block must not be followed by another empty block. + + // We need to merge the previous and next blocks, then + // delete the current and next blocks. Be sure to + // resize the next block to zero to prevent the + // transferred cells to be deleted. + block& blk_prev = m_blocks[block_index - 1]; + + // Check if the next block is bigger. + if (blk_prev.size < blk_next->size) + { + // Prepend the new item to the next block, then + // prepend the content of the previous block and + // release both previous and current blocks. + + size_type position = blk_prev.position; + + // Increase the size of block and prepend the new cell + blk_next->size += 1; + mdds_mtv_prepend_value(*blk_next->data, cell); + + // Preprend the content of previous block to the next block. + block_funcs::prepend_values_from_block(*blk_next->data, *blk_prev.data, 0, blk_prev.size); + blk_next->size += blk_prev.size; + blk_next->position = position; + + // Resize the previous block to zero + block_funcs::resize_block(*blk_prev.data, 0); + m_hdl_event.element_block_released(blk_prev.data); + + // Release both blocks which are no longer used + block_funcs::delete_block(blk->data); + block_funcs::delete_block(blk_prev.data); + + // Get an iterator to previous block + typename blocks_type::iterator it = m_blocks.begin() + block_index - 1; + + // So we can use it to remove the two blocks + m_blocks.erase(it, it + 2); + } + else + { + // Be sure to resize the next block to zero to prevent the + // transferred cells to be deleted. + blk_prev.size += 1 + blk_next->size; + mdds_mtv_append_value(*blk_prev.data, cell); + block_funcs::append_block(*blk_prev.data, *blk_next->data); + block_funcs::resize_block(*blk_next->data, 0); + m_hdl_event.element_block_released(blk_next->data); + block_funcs::delete_block(blk->data); + block_funcs::delete_block(blk_next->data); + typename blocks_type::iterator it = m_blocks.begin() + block_index; + m_blocks.erase(it, it + 2); + } + } + else + { + // Ignore the next block. Just extend the previous block. + delete_element_block(m_blocks[block_index]); + m_blocks.erase(m_blocks.begin() + block_index); + append_cell_to_block(block_index - 1, cell); + } + } + } + else + { + // Extend the previous block to append the cell. + assert(blk->size > 1); + blk->size -= 1; + blk->position += 1; + append_cell_to_block(block_index - 1, cell); + } + + return get_iterator(block_index - 1); + } + else + { + // Cell type is different from the type of the previous block. + if (blk->size == 1) + { + if (block_index == m_blocks.size() - 1) + { + // There is no more block below. Simply turn this empty block into a non-empty one. + create_new_block_with_new_cell(blk->data, cell); + } + else + { + // Check the type of the following non-empty block. + assert(block_index < m_blocks.size() - 1); + block* blk_next = get_next_block_of_type(block_index, cat); + if (blk_next) + { + // Remove this empty block, and prepend the cell to the next block. + blk_next->size += 1; + blk_next->position -= 1; + mdds_mtv_prepend_value(*blk_next->data, cell); + delete_element_block(m_blocks[block_index]); + m_blocks.erase(m_blocks.begin() + block_index); + } + else + { + // Simply turn this empty block into a non-empty one. + create_new_block_with_new_cell(blk->data, cell); + } + } + } + else + { + // Replace the current empty block of size > 1 with a + // non-empty block of size 1, and insert a new empty block + // below whose size is one shorter than the current empty + // block. + size_type new_block_size = blk->size - 1; + size_type new_block_position = blk->position + 1; + blk->size = 1; + create_new_block_with_new_cell(blk->data, cell); + m_blocks.emplace(m_blocks.begin() + block_index + 1, new_block_position, new_block_size); + } + + return get_iterator(block_index); + } + } + else if (pos_in_block == blk->size - 1) + { + // New cell is at the end of the current block. + assert(blk->size > 1); + if (block_index == m_blocks.size() - 1) + { + // The current block is the last block. + blk->size -= 1; + size_type new_position = detail::calc_next_block_position(*blk); + m_blocks.emplace_back(new_position, 1); + blk = &m_blocks.back(); // old pointer is invalid. + create_new_block_with_new_cell(blk->data, cell); + iterator it = end(); + --it; + return it; + } + else + { + // A non-empty block exists below. + element_category_type cat = mdds_mtv_get_element_type(cell); + block* blk_next = get_next_block_of_type(block_index, cat); + if (blk_next) + { + // Shrink this empty block and extend the next block. + blk->size -= 1; + blk_next->size += 1; + blk_next->position -= 1; + mdds_mtv_prepend_value(*blk_next->data, cell); + } + else + { + // Shrink the current empty block by one, and insert a new + // block of size 1 below it to store the new value. + blk->size -= 1; + size_type new_position = detail::calc_next_block_position(*blk); + m_blocks.emplace(m_blocks.begin() + block_index + 1, new_position, 1); + blk = &m_blocks[block_index]; // old pointer is invalid. + block& blk2 = m_blocks[block_index + 1]; + create_new_block_with_new_cell(blk2.data, cell); + } + + return get_iterator(block_index + 1); + } + } + + // New cell is somewhere in the middle of an empty block. + return set_cell_to_middle_of_block(block_index, pos_in_block, cell); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cell_to_block_of_size_one( + size_type block_index, const T& cell) +{ + block* blk = &m_blocks[block_index]; + assert(blk->size == 1); + assert(blk->data); + element_category_type cat = mdds_mtv_get_element_type(cell); + assert(mdds::mtv::get_block_type(*blk->data) != cat); + + if (block_index == 0) + { + // This is the topmost block of size 1. + if (block_index == m_blocks.size() - 1) + { + // This is the only block. + create_new_block_with_new_cell(blk->data, cell); + return begin(); + } + + // There is an existing block below. + block* blk_next = get_next_block_of_type(block_index, cat); + if (!blk_next) + { + // Next block is empty or of different type. + create_new_block_with_new_cell(blk->data, cell); + return begin(); + } + + // Delete the current block, and prepend the cell to the next block. + blk_next->size += 1; + blk_next->position -= 1; + mdds_mtv_prepend_value(*blk_next->data, cell); + delete_element_block(*blk); + m_blocks.erase(m_blocks.begin() + block_index); + return begin(); + } + + assert(block_index > 0); + + if (block_index == m_blocks.size() - 1) + { + // This is the last block, and a block exists above. + block* blk_prev = &m_blocks[block_index - 1]; + if (!blk_prev->data || mdds::mtv::get_block_type(*blk_prev->data) != cat) + { + // Previous block is empty. Replace the current block with a new one. + create_new_block_with_new_cell(blk->data, cell); + } + else + { + // Append the cell to the previos block, and remove the + // current block. + mdds_mtv_append_value(*blk_prev->data, cell); + blk_prev->size += 1; + delete_element_block(*blk); + m_blocks.erase(m_blocks.begin() + block_index); + } + + iterator itr = end(); + --itr; + return itr; + } + + // Remove the current block, and check if the cell can be append + // to the previous block, or prepended to the following block. + // Also check if the blocks above and below need to be combined. + + block* blk_prev = &m_blocks[block_index - 1]; + block* blk_next = &m_blocks[block_index + 1]; + if (!blk_prev->data) + { + // Previous block is empty. + if (!blk_next->data) + { + // Next block is empty too. + create_new_block_with_new_cell(blk->data, cell); + return get_iterator(block_index); + } + + // Previous block is empty, but the next block is not. + element_category_type blk_cat_next = mdds::mtv::get_block_type(*blk_next->data); + if (blk_cat_next == cat) + { + // Delete the current block, and prepend the new cell to the next block. + delete_element_block(*blk); + m_blocks.erase(m_blocks.begin() + block_index); + blk = &m_blocks[block_index]; + blk->size += 1; + blk->position -= 1; + mdds_mtv_prepend_value(*blk->data, cell); + return get_iterator(block_index); + } + + assert(blk_cat_next != cat); + create_new_block_with_new_cell(blk->data, cell); + return get_iterator(block_index); + } + + if (!blk_next->data) + { + // Next block is empty, and the previous block is not. + assert(blk_prev->data); + element_category_type blk_cat_prev = mdds::mtv::get_block_type(*blk_prev->data); + if (blk_cat_prev == cat) + { + // Append to the previous block. + blk_prev->size += 1; + mdds_mtv_append_value(*blk_prev->data, cell); + delete_element_block(*blk); + m_blocks.erase(m_blocks.begin() + block_index); + return get_iterator(block_index - 1); + } + + // Just overwrite the current block. + create_new_block_with_new_cell(blk->data, cell); + return get_iterator(block_index); + } + + assert(blk_prev && blk_prev->data); + assert(blk_next && blk_next->data); + element_category_type blk_cat_prev = mdds::mtv::get_block_type(*blk_prev->data); + element_category_type blk_cat_next = mdds::mtv::get_block_type(*blk_next->data); + + if (blk_cat_prev == blk_cat_next) + { + if (blk_cat_prev == cat) + { + // Merge the previous block with the cell being inserted and + // the next block. Resize the next block to zero to prevent + // deletion of mananged cells on block deletion. + blk_prev->size += 1 + blk_next->size; + mdds_mtv_append_value(*blk_prev->data, cell); + block_funcs::append_block(*blk_prev->data, *blk_next->data); + block_funcs::resize_block(*blk_next->data, 0); + + // Delete the current and next blocks. + delete_element_block(*blk); + delete_element_block(*blk_next); + typename blocks_type::iterator it = m_blocks.begin() + block_index; + typename blocks_type::iterator it_last = it + 2; + m_blocks.erase(it, it_last); + return get_iterator(block_index - 1); + } + + // Just overwrite the current block. + create_new_block_with_new_cell(blk->data, cell); + return get_iterator(block_index); + } + + assert(blk_cat_prev != blk_cat_next); + + if (blk_cat_prev == cat) + { + // Append to the previous block. + blk_prev->size += 1; + mdds_mtv_append_value(*blk_prev->data, cell); + delete_element_block(*blk); + m_blocks.erase(m_blocks.begin() + block_index); + return get_iterator(block_index - 1); + } + + if (blk_cat_next == cat) + { + // Prepend to the next block. + blk_next->size += 1; + blk_next->position -= 1; + mdds_mtv_prepend_value(*blk_next->data, cell); + delete_element_block(*blk); + m_blocks.erase(m_blocks.begin() + block_index); + return get_iterator(block_index); + } + + // Just overwrite the current block. + create_new_block_with_new_cell(blk->data, cell); + return get_iterator(block_index); +} + +template +template +void multi_type_vector::set_cell_to_top_of_data_block(size_type block_index, const T& cell) +{ + block& blk = m_blocks[block_index]; + blk.size -= 1; + size_type position = blk.position; + blk.position += 1; + + if (blk.data) + { + block_funcs::overwrite_values(*blk.data, 0, 1); + block_funcs::erase(*blk.data, 0); + } + + m_blocks.emplace(m_blocks.begin() + block_index, position, 1); + create_new_block_with_new_cell(m_blocks[block_index].data, cell); +} + +template +template +void multi_type_vector::set_cell_to_bottom_of_data_block(size_type block_index, const T& cell) +{ + assert(block_index < m_blocks.size()); + block& blk = m_blocks[block_index]; + if (blk.data) + { + block_funcs::overwrite_values(*blk.data, blk.size - 1, 1); + block_funcs::erase(*blk.data, blk.size - 1); + } + blk.size -= 1; + size_type next_position = detail::calc_next_block_position(blk); + m_blocks.emplace(m_blocks.begin() + block_index + 1, next_position, 1); + create_new_block_with_new_cell(m_blocks[block_index + 1].data, cell); +} + +template +template +void multi_type_vector::get(size_type pos, T& value) const +{ + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::get", __LINE__, pos, block_size(), size()); + + const block* blk = &m_blocks[block_index]; + assert(blk); + + if (!blk->data) + { + // empty cell block. + mdds_mtv_get_empty_value(value); + return; + } + + size_type start_row = blk->position; + assert(pos >= start_row); + assert(blk->data); // data for non-empty blocks should never be nullptr. + size_type idx = pos - start_row; + mdds_mtv_get_value(*blk->data, idx, value); +} + +template +template +T multi_type_vector::get(size_type pos) const +{ + T cell; + get(pos, cell); + return cell; +} + +template +template +T multi_type_vector::release(size_type pos) +{ + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release", __LINE__, pos, block_size(), size()); + + T value; + release_impl(pos, block_index, value); + return value; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::release(size_type pos, T& value) +{ + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release", __LINE__, pos, block_size(), size()); + + return release_impl(pos, block_index, value); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::release( + const iterator& pos_hint, size_type pos, T& value) +{ + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release", __LINE__, pos, block_size(), size()); + + return release_impl(pos, block_index, value); +} + +template +void multi_type_vector::release() +{ + typename blocks_type::iterator it = m_blocks.begin(), it_end = m_blocks.end(); + for (; it != it_end; ++it) + { + block* blk = &(*it); + if (blk->data) + { + block_funcs::resize_block(*blk->data, 0); + m_hdl_event.element_block_released(blk->data); + block_funcs::delete_block(blk->data); + } + } + + m_blocks.clear(); + m_cur_size = 0; +} + +template +typename multi_type_vector::iterator multi_type_vector::release_range( + size_type start_pos, size_type end_pos) +{ + size_type block_index1 = get_block_position(start_pos); + if (block_index1 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release_range", __LINE__, start_pos, block_size(), size()); + + return set_empty_impl(start_pos, end_pos, block_index1, false); +} + +template +typename multi_type_vector::iterator multi_type_vector::release_range( + const iterator& pos_hint, size_type start_pos, size_type end_pos) +{ + size_type block_index1 = get_block_position(pos_hint->__private_data, start_pos); + if (block_index1 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release_range", __LINE__, start_pos, block_size(), size()); + + return set_empty_impl(start_pos, end_pos, block_index1, false); +} + +template +typename multi_type_vector::position_type multi_type_vector::position(size_type pos) +{ + if (pos == m_cur_size) + { + // This is a valid end position. Create a valid position object that + // represents a valid end position. + return position_type(end(), 0); + } + + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::position", __LINE__, pos, block_size(), size()); + + size_type start_pos = m_blocks[block_index].position; + + iterator it = get_iterator(block_index); + return position_type(it, pos - start_pos); +} + +template +typename multi_type_vector::position_type multi_type_vector::position( + const iterator& pos_hint, size_type pos) +{ + if (pos == m_cur_size) + { + // This is a valid end position. Create a valid position object that + // represents a valid end position. + return position_type(end(), 0); + } + + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::position", __LINE__, pos, block_size(), size()); + + iterator it = get_iterator(block_index); + size_type start_pos = m_blocks[block_index].position; + return position_type(it, pos - start_pos); +} + +template +typename multi_type_vector::const_position_type multi_type_vector::position(size_type pos) const +{ + if (pos == m_cur_size) + { + // This is a valid end position. Create a valid position object that + // represents a valid end position. + return const_position_type(cend(), 0); + } + + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::position", __LINE__, pos, block_size(), size()); + + size_type start_row = m_blocks[block_index].position; + + typename blocks_type::const_iterator block_pos = m_blocks.begin(); + std::advance(block_pos, block_index); + const_iterator it = const_iterator(block_pos, m_blocks.end(), this, block_index); + return const_position_type(it, pos - start_row); +} + +template +typename multi_type_vector::const_position_type multi_type_vector::position( + const const_iterator& pos_hint, size_type pos) const +{ + if (pos == m_cur_size) + { + // This is a valid end position. Create a valid position object that + // represents a valid end position. + return const_position_type(cend(), 0); + } + + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::position", __LINE__, pos, block_size(), size()); + + const_iterator it = get_const_iterator(block_index); + size_type start_pos = m_blocks[block_index].position; + return const_position_type(it, pos - start_pos); +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer( + size_type start_pos, size_type end_pos, multi_type_vector& dest, size_type dest_pos) +{ + if (&dest == this) + throw invalid_arg_error("You cannot transfer between the same container."); + + size_type block_index1 = get_block_position(start_pos); + if (block_index1 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::transfer", __LINE__, start_pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block, os_prev_block_dest; + dump_blocks(os_prev_block); + dest.dump_blocks(os_prev_block_dest); +#endif + + iterator ret = transfer_impl(start_pos, end_pos, block_index1, dest, dest_pos); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_block, os_block_dest; + dump_blocks(os_block); + dest.dump_blocks(os_block_dest); + + try + { + check_block_integrity(); + dest.check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << std::endl + << "block integrity check failed in transfer (start_pos=" << start_pos << "; end_pos=" << end_pos + << "; dest_pos=" << dest_pos << ")" << std::endl; + os << std::endl << "previous block state (source):" << std::endl; + os << os_prev_block.str(); + os << std::endl << "previous block state (destination):" << std::endl; + os << os_prev_block_dest.str(); + os << std::endl << "altered block state (source):" << std::endl; + os << os_block.str(); + os << std::endl << "altered block state (destination):" << std::endl; + os << os_block_dest.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer( + const iterator& pos_hint, size_type start_pos, size_type end_pos, multi_type_vector& dest, size_type dest_pos) +{ + size_type block_index1 = get_block_position(pos_hint->__private_data, start_pos); + if (block_index1 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::transfer", __LINE__, start_pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block, os_prev_block_dest; + dump_blocks(os_prev_block); + dest.dump_blocks(os_prev_block_dest); +#endif + + iterator ret = transfer_impl(start_pos, end_pos, block_index1, dest, dest_pos); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_block, os_block_dest; + dump_blocks(os_block); + dest.dump_blocks(os_block_dest); + + try + { + check_block_integrity(); + dest.check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << std::endl + << "block integrity check failed in transfer (start_pos=" << start_pos << "; end_pos=" << end_pos + << "; dest_pos=" << dest_pos << ")" << std::endl; + os << std::endl << "previous block state (source):" << std::endl; + os << os_prev_block.str(); + os << std::endl << "previous block state (destination):" << std::endl; + os << os_prev_block_dest.str(); + os << std::endl << "altered block state (source):" << std::endl; + os << os_block.str(); + os << std::endl << "altered block state (destination):" << std::endl; + os << os_block_dest.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +mtv::element_t multi_type_vector::get_type(size_type pos) const +{ + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::get_type", __LINE__, pos, block_size(), size()); + + const block* blk = &m_blocks[block_index]; + if (!blk->data) + return mtv::element_type_empty; + + return mtv::get_block_type(*blk->data); +} + +template +bool multi_type_vector::is_empty(size_type pos) const +{ + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::is_empty", __LINE__, pos, block_size(), size()); + + return m_blocks[block_index].data == nullptr; +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty( + size_type start_pos, size_type end_pos) +{ + size_type block_index1 = get_block_position(start_pos); + if (block_index1 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set_empty", __LINE__, start_pos, block_size(), size()); + + return set_empty_impl(start_pos, end_pos, block_index1, true); +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty( + const iterator& pos_hint, size_type start_pos, size_type end_pos) +{ + size_type block_index1 = get_block_position(pos_hint->__private_data, start_pos); + if (block_index1 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set_empty", __LINE__, start_pos, block_size(), size()); + + return set_empty_impl(start_pos, end_pos, block_index1, true); +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer_impl( + size_type start_pos, size_type end_pos, size_type block_index1, multi_type_vector& dest, size_type dest_pos) +{ + if (start_pos > end_pos) + { + std::ostringstream os; + os << "multi_type_vector::transfer_impl: start position is larger than the end position. (start="; + os << start_pos << ", end=" << end_pos << ")"; + throw std::out_of_range(os.str()); + } + + size_type block_index2 = get_block_position(end_pos, block_index1); + if (block_index2 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::transfer_impl", __LINE__, end_pos, block_size(), size()); + + size_type len = end_pos - start_pos + 1; + size_type last_dest_pos = dest_pos + len - 1; + + // Make sure the destination container is large enough. + if (last_dest_pos >= dest.size()) + throw std::out_of_range("Destination vector is too small for the elements being transferred."); + + if (block_index1 == block_index2) + { + // All elements are in the same block. + return transfer_single_block(start_pos, end_pos, block_index1, dest, dest_pos); + } + + return transfer_multi_blocks(start_pos, end_pos, block_index1, block_index2, dest, dest_pos); +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer_single_block( + size_type start_pos, size_type end_pos, size_type block_index1, multi_type_vector& dest, size_type dest_pos) +{ + size_type len = end_pos - start_pos + 1; + size_type last_dest_pos = dest_pos + len - 1; + + // All elements are in the same block. + block* blk = &m_blocks[block_index1]; + size_type start_pos_in_block1 = blk->position; + + // Empty the region in the destination container where the elements + // are to be transferred to. This ensures that the destination region + // consists of a single block. + iterator it_dest_blk = dest.set_empty(dest_pos, last_dest_pos); + + if (!blk->data) + return get_iterator(block_index1); + + element_category_type cat = get_block_type(*blk->data); + + size_type dest_block_index = it_dest_blk->__private_data.block_index; + block* blk_dest = &dest.m_blocks[dest_block_index]; + + size_type dest_pos_in_block = dest_pos - it_dest_blk->position; + if (dest_pos_in_block == 0) + { + // Copy to the top part of the destination block. + + assert(!blk_dest->data); // should be already emptied. + + if (len < blk_dest->size) + { + // Shrink the existing block and insert a new block before it. + assert(len < blk_dest->size); + size_type position = blk_dest->position; + blk_dest->position += len; + blk_dest->size -= len; + dest.m_blocks.emplace(dest.m_blocks.begin() + dest_block_index, position, len); + blk_dest = &dest.m_blocks[dest_block_index]; // The old pointer is invalid. + } + } + else if (dest_pos_in_block + len - 1 == it_dest_blk->size - 1) + { + // Copy to the bottom part of destination block. + + // Insert a new block below current, and shrink the current block. + dest.m_blocks[dest_block_index].size -= len; + size_type position = detail::calc_next_block_position(dest.m_blocks, dest_block_index); + dest.m_blocks.emplace(dest.m_blocks.begin() + dest_block_index + 1, position, len); + blk_dest = &dest.m_blocks[dest_block_index + 1]; + ++dest_block_index; // Must point to the new copied block. + } + else + { + // Copy to the middle of destination block. + + // Insert two new blocks below current. + size_type blk2_size = blk_dest->size - dest_pos_in_block - len; + dest.m_blocks.insert(dest.m_blocks.begin() + dest_block_index + 1, 2u, block()); + dest.m_blocks[dest_block_index].size = dest_pos_in_block; + dest.m_blocks[dest_block_index + 1].size = len; + dest.m_blocks[dest_block_index + 2].size = blk2_size; + + dest.m_blocks[dest_block_index + 1].position = + detail::calc_next_block_position(dest.m_blocks, dest_block_index); + dest.m_blocks[dest_block_index + 2].position = + detail::calc_next_block_position(dest.m_blocks, dest_block_index + 1); + + blk_dest = &dest.m_blocks[dest_block_index + 1]; + + ++dest_block_index; // Must point to the new copied block. + } + + assert(blk_dest->size == len); + size_type offset = start_pos - start_pos_in_block1; + if (offset == 0 && len == blk->size) + { + // Just move the whole data array. + blk_dest->data = blk->data; + dest.m_hdl_event.element_block_acquired(blk_dest->data); + + m_hdl_event.element_block_released(blk->data); + blk->data = nullptr; + + dest.merge_with_adjacent_blocks(dest_block_index); + size_type start_pos_offset = merge_with_adjacent_blocks(block_index1); + if (start_pos_offset) + { + // Merged with the previous block. Adjust the return block position. + --block_index1; + start_pos_in_block1 -= start_pos_offset; + } + + return get_iterator(block_index1); + } + + blk_dest->data = block_funcs::create_new_block(cat, 0); + assert(blk_dest->data); + dest.m_hdl_event.element_block_acquired(blk_dest->data); + + // Shallow-copy the elements to the destination block. + block_funcs::assign_values_from_block(*blk_dest->data, *blk->data, offset, len); + dest.merge_with_adjacent_blocks(dest_block_index); + + // Set the source range empty without overwriting the elements. + return set_empty_in_single_block(start_pos, end_pos, block_index1, false); +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer_multi_blocks( + size_type start_pos, size_type end_pos, size_type block_index1, size_type block_index2, multi_type_vector& dest, + size_type dest_pos) +{ + assert(block_index1 < block_index2); + size_type start_pos_in_block1 = m_blocks[block_index1].position; + size_type start_pos_in_block2 = m_blocks[block_index2].position; + + size_type len = end_pos - start_pos + 1; + size_type last_dest_pos = dest_pos + len - 1; + + // Empty the region in the destination container where the elements + // are to be transferred to. This ensures that the destination region + // consists of a single block. + iterator it_dest_blk = dest.set_empty(dest_pos, last_dest_pos); + + size_type dest_block_index = it_dest_blk->__private_data.block_index; + size_type dest_pos_in_block = dest_pos - it_dest_blk->position; + block* blk_dest = &dest.m_blocks[dest_block_index]; + assert(!blk_dest->data); // should be already emptied. + + size_type block_len = block_index2 - block_index1 + 1; + + // Create slots for new blocks in the destination. + + size_type dest_block_index1 = dest_block_index; + + if (dest_pos_in_block == 0) + { + // Copy to the top part of destination block. + if (len < blk_dest->size) + { + // Shrink the existing block and insert slots for the new blocks before it. + blk_dest->size -= len; + blk_dest->position += len; + dest.m_blocks.insert(dest.m_blocks.begin() + dest_block_index, block_len, block()); + } + else + { + // Destination block is exactly of the length of the elements being transferred. + dest.delete_element_block(*blk_dest); + blk_dest->size = 0; + if (block_len > 1) + dest.m_blocks.insert(dest.m_blocks.begin() + dest_block_index, block_len - 1, block()); + } + } + else if (dest_pos_in_block + len - 1 == it_dest_blk->size - 1) + { + // Copy to the bottom part of destination block. Insert slots for new + // blocks below current, and shrink the current block. + dest.m_blocks.insert(dest.m_blocks.begin() + dest_block_index + 1, block_len, block()); + blk_dest = &dest.m_blocks[dest_block_index]; + blk_dest->size -= len; + + ++dest_block_index1; + } + else + { + // Copy to the middle of the destination block. Insert slots for the + // new blocks (plus one extra for the bottom empty block) below the + // current block. + size_type blk2_size = blk_dest->size - dest_pos_in_block - len; + dest.m_blocks.insert(dest.m_blocks.begin() + dest_block_index + 1, block_len + 1, block()); + blk_dest = &dest.m_blocks[dest_block_index]; + assert(dest.m_blocks.size() > dest_block_index + block_len + 1); + blk_dest->size = dest_pos_in_block; + + // Re-calculate the size and position of the lower part of the destination block. + block& blk_dest_lower = dest.m_blocks[dest_block_index + block_len + 1]; + blk_dest_lower.position = detail::calc_next_block_position(*blk_dest) + len; + blk_dest_lower.size = blk2_size; + + ++dest_block_index1; + } + + size_type del_index1 = block_index1, del_index2 = block_index2; + + // Now that the new slots have been created, start transferring the blocks. + + // Transfer the first block. + size_type offset = start_pos - start_pos_in_block1; + if (offset) + { + // Transfer the lower part of the first block. + block* blk = &m_blocks[block_index1]; + + assert(dest.m_blocks[dest_block_index1].size == 0); + dest.m_blocks[dest_block_index1].size = blk->size - offset; + if (dest_block_index1 > 0) + dest.m_blocks[dest_block_index1].position = + detail::calc_next_block_position(dest.m_blocks, dest_block_index1 - 1); + + if (blk->data) + { + element_category_type cat = mtv::get_block_type(*blk->data); + blk_dest = &dest.m_blocks[dest_block_index1]; + blk_dest->data = block_funcs::create_new_block(cat, 0); + assert(blk_dest->data); + dest.m_hdl_event.element_block_acquired(blk_dest->data); + + // Shallow-copy the elements to the destination block, and shrink + // the source block to remove the transferred elements. + block_funcs::assign_values_from_block(*blk_dest->data, *blk->data, offset, blk->size - offset); + block_funcs::resize_block(*blk->data, offset); + } + + blk->size = offset; + ++del_index1; // Retain this block. + } + else + { + // Just move the whole block over. + block& blk = m_blocks[block_index1]; + dest.m_blocks[dest_block_index1] = blk; // copied. + dest.m_blocks[dest_block_index1].position = + dest_block_index1 > 0 ? detail::calc_next_block_position(dest.m_blocks, dest_block_index1 - 1) : 0; + + if (blk.data) + { + dest.m_hdl_event.element_block_acquired(blk.data); + m_hdl_event.element_block_released(blk.data); + blk.data = nullptr; + } + + blk.size = 0; + } + + if (block_len > 2) + { + // Transfer all blocks in between. + size_type position = detail::calc_next_block_position(dest.m_blocks, dest_block_index1); + for (size_type i = 0; i < block_len - 2; ++i) + { + size_type src_block_pos = block_index1 + 1 + i; + size_type dest_block_pos = dest_block_index1 + 1 + i; + assert(dest.m_blocks[dest_block_pos].size == 0); + block& blk = m_blocks[src_block_pos]; + dest.m_blocks[dest_block_pos] = blk; // copied. + dest.m_blocks[dest_block_pos].position = position; + position += blk.size; + blk.size = 0; + + if (blk.data) + { + dest.m_hdl_event.element_block_acquired(blk.data); + m_hdl_event.element_block_released(blk.data); + blk.data = nullptr; + } + } + } + + // Transfer the last block. + if (block_len > 1) + { + size_type size_to_trans = end_pos - start_pos_in_block2 + 1; + size_type dest_block_pos = dest_block_index1 + block_len - 1; + assert(dest.m_blocks[dest_block_pos].size == 0); + + block& blk = m_blocks[block_index2]; + if (size_to_trans < blk.size) + { + // Transfer the upper part of this block. + assert(dest_block_pos > 0); + dest.m_blocks[dest_block_pos].position = + detail::calc_next_block_position(dest.m_blocks, dest_block_pos - 1); + dest.m_blocks[dest_block_pos].size = size_to_trans; + blk_dest = &dest.m_blocks[dest_block_pos]; + if (blk.data) + { + element_category_type cat = mtv::get_block_type(*blk.data); + blk_dest->data = block_funcs::create_new_block(cat, 0); + dest.m_hdl_event.element_block_acquired(blk_dest->data); + + block_funcs::assign_values_from_block(*blk_dest->data, *blk.data, 0, size_to_trans); + block_funcs::erase(*blk.data, 0, size_to_trans); + } + + blk.position += size_to_trans; + blk.size -= size_to_trans; + --del_index2; // Retain this block. + } + else + { + // Just move the whole block over. + dest.m_blocks[dest_block_pos] = blk; + dest.m_blocks[dest_block_pos].position = + dest_block_pos > 0 ? detail::calc_next_block_position(dest.m_blocks, dest_block_pos - 1) : 0; + + if (blk.data) + { + dest.m_hdl_event.element_block_acquired(blk.data); + m_hdl_event.element_block_released(blk.data); + blk.data = nullptr; + } + m_blocks[block_index2].size = 0; + } + } + + // Now that all the elements have been transferred, check the bordering + // blocks in the destination and merge them as needed. + if (block_len > 1) + dest.merge_with_adjacent_blocks(dest_block_index1 + block_len - 1); + dest.merge_with_adjacent_blocks(dest_block_index1); + + // Delete all transferred blocks, and replace it with one empty block. + if (del_index2 < del_index1) + { + // No blocks will be deleted. See if we can just extend one of the + // neighboring empty blocks. + + block& blk1 = m_blocks[block_index1]; + block& blk2 = m_blocks[block_index2]; + + if (!blk1.data) + { + assert(blk2.data); + + // Block 1 is empty. Extend this block downward. + blk1.size += len; + return get_iterator(block_index1); + } + + if (!blk2.data) + { + assert(blk1.data); + + // Block 2 is empty. Extend this block upward. + blk2.size += len; + blk2.position -= len; + return get_iterator(block_index2); + } + + // Neither block1 nor block2 are empty. Just insert a new empty block + // between them. After the insertion, the old block2 position becomes + // the position of the inserted block. + size_type position = detail::calc_next_block_position(blk1); + m_blocks.emplace(m_blocks.begin() + block_index2, position, len); + // No need to adjust local index vars + return get_iterator(block_index2); + } + + if (del_index1 > 0 && !m_blocks[del_index1 - 1].data) + { + // The block before the first block to be deleted is empty. Simply + // extend that block to cover the deleted block segment. + block& blk_prev = m_blocks[del_index1 - 1]; + + // Extend the previous block. + blk_prev.size += len; + } + else + { + // Block before is not empty (or doesn't exist). Keep the first slot, + // and erase the rest. + m_blocks[del_index1].size = len; // Insert an empty + ++del_index1; + } + + size_type ret_block_index = del_index1 - 1; + + if (del_index2 >= del_index1) + { + typename blocks_type::iterator it_blk = m_blocks.begin(); + typename blocks_type::iterator it_blk_end = m_blocks.begin(); + std::advance(it_blk, del_index1); + std::advance(it_blk_end, del_index2 + 1); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + typename blocks_type::iterator it_test = it_blk; + for (; it_test != it_blk_end; ++it_test) + { + // All slots to be erased should have zero size + assert(it_test->size == 0); + } +#endif + m_blocks.erase(it_blk, it_blk_end); + } + + // The block pointed to by ret_block_index is guaranteed to be empty by + // this point. + assert(!m_blocks[ret_block_index].data); + // Merging with the previous block never happens. + size_type start_pos_offset = merge_with_adjacent_blocks(ret_block_index); + (void)start_pos_offset; // avoid unused variable compiler warning. + assert(!start_pos_offset); + + m_blocks[ret_block_index].position = + ret_block_index > 0 ? detail::calc_next_block_position(m_blocks, ret_block_index - 1) : 0; + + return get_iterator(ret_block_index); +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty_impl( + size_type start_pos, size_type end_pos, size_type block_index1, bool overwrite) +{ + if (start_pos > end_pos) + throw std::out_of_range("Start row is larger than the end row."); + + size_type block_index2 = get_block_position(end_pos, block_index1); + if (block_index2 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set_empty_impl", __LINE__, end_pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret_it; + if (block_index1 == block_index2) + ret_it = set_empty_in_single_block(start_pos, end_pos, block_index1, overwrite); + else + ret_it = set_empty_in_multi_blocks(start_pos, end_pos, block_index1, block_index2, overwrite); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in set_empty(start_pos=" << start_pos << "; end_pos=" << end_pos << ")" + << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + return ret_it; +} + +template +void multi_type_vector::swap_impl( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index1, + size_type block_index2, size_type dblock_index1, size_type dblock_index2) +{ + if (block_index1 == block_index2) + { + // Source range is in a single block. + if (dblock_index1 == dblock_index2) + { + // Destination range is also in a single block. + swap_single_block(other, start_pos, end_pos, other_pos, block_index1, dblock_index1); + } + else + { + // Source is single-, and destination is multi-blocks. + swap_single_to_multi_blocks( + other, start_pos, end_pos, other_pos, block_index1, dblock_index1, dblock_index2); + } + } + else if (dblock_index1 == dblock_index2) + { + // Destination range is over a single block. Switch source and destination. + size_type len = end_pos - start_pos + 1; + other.swap_single_to_multi_blocks( + *this, other_pos, other_pos + len - 1, start_pos, dblock_index1, block_index1, block_index2); + } + else + { + // Both source and destinations are multi-block. + swap_multi_to_multi_blocks( + other, start_pos, end_pos, other_pos, block_index1, block_index2, dblock_index1, dblock_index2); + } +} + +template +void multi_type_vector::swap_single_block( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index, + size_type other_block_index) +{ + block* blk_src = &m_blocks[block_index]; + block* blk_dst = &other.m_blocks[other_block_index]; + size_type start_pos_in_block = blk_src->position; + size_type start_pos_in_other_block = blk_dst->position; + element_category_type cat_src = mtv::element_type_empty; + element_category_type cat_dst = mtv::element_type_empty; + + if (blk_src->data) + cat_src = mtv::get_block_type(*blk_src->data); + if (blk_dst->data) + cat_dst = mtv::get_block_type(*blk_dst->data); + + size_t other_end_pos = other_pos + end_pos - start_pos; + size_t len = end_pos - start_pos + 1; // length of elements to swap. + size_type src_offset = start_pos - start_pos_in_block; + size_type dst_offset = other_pos - start_pos_in_other_block; + + // length of the tail that will not be swapped. + size_type src_tail_len = blk_src->size - src_offset - len; + + if (cat_src == cat_dst) + { + // Source and destination blocks are of the same type. + if (cat_src == mtv::element_type_empty) + // Both are empty blocks. Nothing to swap. + return; + + block_funcs::swap_values(*blk_src->data, *blk_dst->data, src_offset, dst_offset, len); + return; + } + + // Source and destination blocks are of different types. + + if (cat_src == mtv::element_type_empty) + { + // Source is empty but destination is not. This is equivalent of transfer. + other.transfer_single_block(other_pos, other_end_pos, other_block_index, *this, start_pos); + // No update of local index vars needed. + return; + } + + if (cat_dst == mtv::element_type_empty) + { + // Source is not empty but destination is. Use transfer. + transfer_single_block(start_pos, end_pos, block_index, other, other_pos); + // No update of local index vars needed. + return; + } + + // Neither the source nor destination blocks are empty, and they are of different types. + if (src_offset == 0) + { + // Source range is at the top of a block. + if (src_tail_len == 0) + { + // the whole block needs to be replaced. + std::unique_ptr src_data(blk_src->data); + m_hdl_event.element_block_released(blk_src->data); + blk_src->data = other.exchange_elements(*src_data, src_offset, other_block_index, dst_offset, len); + m_hdl_event.element_block_acquired(blk_src->data); + + // Release elements in the source block to prevent double-deletion. + block_funcs::resize_block(*src_data, 0); + merge_with_adjacent_blocks(block_index); + return; + } + + // Get the new elements from the other container. + std::unique_ptr dst_data( + other.exchange_elements(*blk_src->data, src_offset, other_block_index, dst_offset, len)); + + // Shrink the current block by erasing the top part. + block_funcs::erase(*blk_src->data, 0, len); + blk_src->position += len; + blk_src->size -= len; + + block* blk_prev = get_previous_block_of_type(block_index, cat_dst); + if (blk_prev) + { + // Append the new elements to the previous block. + block_funcs::append_block(*blk_prev->data, *dst_data); + block_funcs::resize_block(*dst_data, 0); // prevent double-delete. + blk_prev->size += len; + } + else + { + // Insert a new block to store the new elements. + size_type position = blk_src->position - len; + m_blocks.emplace(m_blocks.begin() + block_index, position, len); + block& blk = m_blocks[block_index]; + blk.data = dst_data.release(); + m_hdl_event.element_block_acquired(blk.data); + } + return; + } + + // Get the new elements from the other container. + std::unique_ptr dst_data( + other.exchange_elements(*blk_src->data, src_offset, other_block_index, dst_offset, len)); + + if (src_tail_len == 0) + { + // Source range is at the bottom of a block. + + // Shrink the current block. + block_funcs::resize_block(*blk_src->data, src_offset); + blk_src->size = src_offset; + + block* blk_next = get_next_block_of_type(block_index, cat_dst); + if (blk_next) + { + // Merge with the next block. + block_funcs::prepend_values_from_block(*blk_next->data, *dst_data, 0, len); + block_funcs::resize_block(*dst_data, 0); // prevent double-delete. + blk_next->size += len; + blk_next->position -= len; + } + else + { + size_type position = detail::calc_next_block_position(*blk_src); + m_blocks.emplace(m_blocks.begin() + block_index + 1, position, len); + block& blk = m_blocks[block_index + 1]; + blk.data = dst_data.release(); + m_hdl_event.element_block_acquired(blk.data); + } + return; + } + + // Source range is in the middle of a block. + assert(src_offset && src_tail_len); + block& blk = set_new_block_to_middle(block_index, src_offset, len, false); + blk.data = dst_data.release(); + m_hdl_event.element_block_acquired(blk.data); +} + +template +void multi_type_vector::swap_single_to_multi_blocks( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index, + size_type dst_block_index1, size_type dst_block_index2) +{ + block* blk_src = &m_blocks[block_index]; + size_type start_pos_in_block = blk_src->position; + size_type dst_start_pos_in_block1 = other.m_blocks[dst_block_index1].position; + size_type dst_start_pos_in_block2 = other.m_blocks[dst_block_index2].position; + + element_category_type cat_src = mtv::element_type_empty; + if (blk_src->data) + cat_src = mtv::get_block_type(*blk_src->data); + + size_type len = end_pos - start_pos + 1; + + if (cat_src == mtv::element_type_empty) + { + // The source block is empty. Use transfer. + other.transfer_multi_blocks( + other_pos, other_pos + len - 1, dst_block_index1, dst_block_index2, *this, start_pos); + return; + } + + size_type src_offset = start_pos - start_pos_in_block; + size_type dst_offset1 = other_pos - dst_start_pos_in_block1; + size_type dst_offset2 = other_pos + len - 1 - dst_start_pos_in_block2; + + // length of the tail that will not be swapped. + size_type src_tail_len = blk_src->size - src_offset - len; + + // Get the new elements from the other container. + blocks_type new_blocks; + other.exchange_elements( + *blk_src->data, src_offset, dst_block_index1, dst_offset1, dst_block_index2, dst_offset2, len, new_blocks); + + if (new_blocks.empty()) + throw general_error("multi_type_vector::swap_single_to_multi_blocks: failed to exchange elements."); + + if (src_offset == 0) + { + // Source range is at the top of a block. + + size_type src_position = blk_src->position; + + if (src_tail_len == 0) + { + // the whole block needs to be replaced. Delete the block, but + // don't delete the managed elements the block contains since they + // have been transferred over to the destination block. + block_funcs::resize_block(*blk_src->data, 0); + delete_element_block(*blk_src); + m_blocks.erase(m_blocks.begin() + block_index); + } + else + { + // Shrink the current block by erasing the top part. + block_funcs::erase(*blk_src->data, 0, len); + blk_src->size -= len; + blk_src->position += len; + } + + insert_blocks_at(src_position, block_index, new_blocks); + merge_with_next_block(block_index + new_blocks.size() - 1); // last block inserted. + if (block_index > 0) + merge_with_next_block(block_index - 1); // block before the first block inserted. + + return; + } + + size_type position = 0; + + if (src_tail_len == 0) + { + // Source range is at the bottom of a block. + + // Shrink the current block. + block_funcs::resize_block(*blk_src->data, src_offset); + blk_src->size = src_offset; + position = detail::calc_next_block_position(*blk_src); + } + else + { + // Source range is in the middle of a block. + assert(src_offset && src_tail_len); + + // This will create two new slots at block_index+1, the first of which + // we will immediately remove. The new blocks from the other + // container will be inserted at the removed slot. + set_new_block_to_middle(block_index, src_offset, len, false); + delete_element_block(m_blocks[block_index + 1]); + m_blocks.erase(m_blocks.begin() + block_index + 1); + position = detail::calc_next_block_position(m_blocks, block_index); + } + + insert_blocks_at(position, block_index + 1, new_blocks); + merge_with_next_block(block_index + new_blocks.size()); // last block inserted. + merge_with_next_block(block_index); // block before the first block inserted. +} + +template +void multi_type_vector::swap_multi_to_multi_blocks( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index1, + size_type block_index2, size_type dblock_index1, size_type dblock_index2) +{ + assert(block_index1 < block_index2); + assert(dblock_index1 < dblock_index2); + + size_type start_pos_in_block1 = m_blocks[block_index1].position; + size_type start_pos_in_block2 = m_blocks[block_index2].position; + size_type start_pos_in_dblock1 = other.m_blocks[dblock_index1].position; + size_type start_pos_in_dblock2 = other.m_blocks[dblock_index2].position; + + size_type len = end_pos - start_pos + 1; + size_type src_offset1 = start_pos - start_pos_in_block1; + size_type src_offset2 = end_pos - start_pos_in_block2; + size_type dst_offset1 = other_pos - start_pos_in_dblock1; + size_type dst_offset2 = other_pos + len - 1 - start_pos_in_dblock2; + + blocks_to_transfer src_bucket, dst_bucket; + prepare_blocks_to_transfer(src_bucket, block_index1, src_offset1, block_index2, src_offset2); + other.prepare_blocks_to_transfer(dst_bucket, dblock_index1, dst_offset1, dblock_index2, dst_offset2); + + size_type position = + src_bucket.insert_index > 0 ? detail::calc_next_block_position(m_blocks, src_bucket.insert_index - 1) : 0; + insert_blocks_at(position, src_bucket.insert_index, dst_bucket.blocks); + + // Merge the boundary blocks in the source. + merge_with_next_block(src_bucket.insert_index + dst_bucket.blocks.size() - 1); + if (src_bucket.insert_index > 0) + merge_with_next_block(src_bucket.insert_index - 1); + + position = + dst_bucket.insert_index > 0 ? detail::calc_next_block_position(other.m_blocks, dst_bucket.insert_index - 1) : 0; + other.insert_blocks_at(position, dst_bucket.insert_index, src_bucket.blocks); + + // Merge the boundary blocks in the destination. + other.merge_with_next_block(dst_bucket.insert_index + src_bucket.blocks.size() - 1); + if (dst_bucket.insert_index > 0) + other.merge_with_next_block(dst_bucket.insert_index - 1); +} + +template +void multi_type_vector::insert_blocks_at(size_type position, size_type insert_pos, blocks_type& new_blocks) +{ + std::for_each(new_blocks.begin(), new_blocks.end(), [&](block& r) { + r.position = position; + position += r.size; + + if (r.data) + m_hdl_event.element_block_acquired(r.data); + }); + + m_blocks.insert(m_blocks.begin() + insert_pos, new_blocks.begin(), new_blocks.end()); +} + +template +void multi_type_vector::prepare_blocks_to_transfer( + blocks_to_transfer& bucket, size_type block_index1, size_type offset1, size_type block_index2, size_type offset2) +{ + assert(block_index1 < block_index2); + assert(offset1 < m_blocks[block_index1].size); + assert(offset2 < m_blocks[block_index2].size); + + block block_first; + block block_last; + typename blocks_type::iterator it_begin = m_blocks.begin(); + typename blocks_type::iterator it_end = m_blocks.begin(); + + std::advance(it_begin, block_index1 + 1); + std::advance(it_end, block_index2); + bucket.insert_index = block_index1 + 1; + + if (offset1 == 0) + { + // The whole first block needs to be swapped. + --it_begin; + --bucket.insert_index; + } + else + { + // Copy the lower part of the block for transfer. + block* blk = &m_blocks[block_index1]; + size_type blk_size = blk->size - offset1; + block_first.size = blk_size; + if (blk->data) + { + block_first.data = block_funcs::create_new_block(mtv::get_block_type(*blk->data), 0); + block_funcs::assign_values_from_block(*block_first.data, *blk->data, offset1, blk_size); + + // Shrink the existing block. + block_funcs::resize_block(*blk->data, offset1); + } + + blk->size = offset1; + } + + block* blk = &m_blocks[block_index2]; + if (offset2 == blk->size - 1) + { + // The whole last block needs to be swapped. + ++it_end; + } + else + { + // Copy the upper part of the block for transfer. + size_type blk_size = offset2 + 1; + block_last.size = blk_size; + if (blk->data) + { + block_last.data = block_funcs::create_new_block(mtv::get_block_type(*blk->data), 0); + block_funcs::assign_values_from_block(*block_last.data, *blk->data, 0, blk_size); + + // Shrink the existing block. + block_funcs::erase(*blk->data, 0, blk_size); + } + + blk->position += blk_size; + blk->size -= blk_size; + } + + // Copy all blocks into the bucket. + if (block_first.size) + bucket.blocks.push_back(std::move(block_first)); + + std::for_each(it_begin, it_end, [&](block& r) { + if (r.data) + m_hdl_event.element_block_released(r.data); + bucket.blocks.push_back(r); + }); + + if (block_last.size) + bucket.blocks.push_back(std::move(block_last)); + + // Remove the slots for these blocks (but don't delete the blocks). + m_blocks.erase(it_begin, it_end); +} + +template +void multi_type_vector::erase(size_type start_pos, size_type end_pos) +{ + if (start_pos > end_pos) + throw std::out_of_range("Start row is larger than the end row."); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + erase_impl(start_pos, end_pos); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in erase (" << start_pos << "-" << end_pos << ")" << std::endl; + os << "block integrity check failed in push_back" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +void multi_type_vector::erase_impl(size_type start_row, size_type end_row) +{ + assert(start_row <= end_row); + + // Keep the logic similar to set_empty(). + + size_type block_pos1 = get_block_position(start_row); + if (block_pos1 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::erase_impl", __LINE__, start_row, block_size(), size()); + + size_type block_pos2 = get_block_position(end_row, block_pos1); + if (block_pos2 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::erase_impl", __LINE__, start_row, block_size(), size()); + + size_type start_row_in_block1 = m_blocks[block_pos1].position; + size_type start_row_in_block2 = m_blocks[block_pos2].position; + + if (block_pos1 == block_pos2) + { + erase_in_single_block(start_row, end_row, block_pos1); + return; + } + + assert(block_pos1 < block_pos2); + + // Initially, we set to erase all blocks between the first and the last. + typename blocks_type::iterator it_erase_begin = m_blocks.begin() + block_pos1 + 1; + typename blocks_type::iterator it_erase_end = m_blocks.begin() + block_pos2; + + // First, inspect the first block. + if (start_row_in_block1 == start_row) + { + // Erase the whole block. + --it_erase_begin; + } + else + { + // Erase the lower part of the first block. + block* blk = &m_blocks[block_pos1]; + size_type new_size = start_row - start_row_in_block1; + if (blk->data) + { + // Shrink the data array. + block_funcs::overwrite_values(*blk->data, new_size, blk->size - new_size); + block_funcs::resize_block(*blk->data, new_size); + } + blk->size = new_size; + } + + size_type adjust_block_offset = 0; + + // Then inspect the last block. + block* blk = &m_blocks[block_pos2]; + size_type last_row_in_block = start_row_in_block2 + blk->size - 1; + if (last_row_in_block == end_row) + { + // Delete the whole block. + ++it_erase_end; + } + else + { + size_type size_to_erase = end_row - start_row_in_block2 + 1; + blk->size -= size_to_erase; + blk->position = start_row; + if (blk->data) + { + // Erase the upper part. + block_funcs::overwrite_values(*blk->data, 0, size_to_erase); + block_funcs::erase(*blk->data, 0, size_to_erase); + } + + adjust_block_offset = 1; // Exclude this block from later block position adjustment. + } + + // Get the index of the block that sits before the blocks being erased. + block_pos1 = std::distance(m_blocks.begin(), it_erase_begin); + if (block_pos1 > 0) + --block_pos1; + + // Now, erase all blocks in between. + delete_element_blocks(it_erase_begin, it_erase_end); + auto it_adjust_block = m_blocks.erase(it_erase_begin, it_erase_end); + int64_t delta = end_row - start_row + 1; + m_cur_size -= delta; + + if (m_blocks.empty()) + return; + + size_t adjust_pos = std::distance(m_blocks.begin(), it_adjust_block); + adjust_pos += adjust_block_offset; + adjust_block_positions_func{}(m_blocks, adjust_pos, -delta); + merge_with_next_block(block_pos1); +} + +template +void multi_type_vector::erase_in_single_block(size_type start_pos, size_type end_pos, size_type block_pos) +{ + // Range falls within the same block. + block* blk = &m_blocks[block_pos]; + int64_t size_to_erase = end_pos - start_pos + 1; + if (blk->data) + { + // Erase data in the data block. + size_type offset = start_pos - blk->position; + block_funcs::overwrite_values(*blk->data, offset, size_to_erase); + block_funcs::erase(*blk->data, offset, size_to_erase); + } + + blk->size -= size_to_erase; + m_cur_size -= size_to_erase; + + if (blk->size) + { + // Block still contains data. Bail out. + adjust_block_positions_func{}(m_blocks, block_pos + 1, -size_to_erase); + return; + } + + // Delete the current block since it's become empty. + delete_element_block(*blk); + m_blocks.erase(m_blocks.begin() + block_pos); + // No need to update blk_index which is not used again. + + if (block_pos == 0) + { + // Deleted block was the first block. + adjust_block_positions_func{}(m_blocks, block_pos, -size_to_erase); + return; + } + + if (block_pos >= m_blocks.size()) + // Deleted block was the last block. + return; + + // Check the previous and next blocks to see if they should be merged. + block* blk_prev = &m_blocks[block_pos - 1]; + block* blk_next = &m_blocks[block_pos]; + if (blk_prev->data) + { + // Previous block has data. + if (!blk_next->data) + { + // Next block is empty. Nothing to do. + adjust_block_positions_func{}(m_blocks, block_pos, -size_to_erase); + return; + } + + element_category_type cat1 = mdds::mtv::get_block_type(*blk_prev->data); + element_category_type cat2 = mdds::mtv::get_block_type(*blk_next->data); + if (cat1 == cat2) + { + // Merge the two blocks. + block_funcs::append_block(*blk_prev->data, *blk_next->data); + blk_prev->size += blk_next->size; + // Resize to 0 to prevent deletion of cells in case of managed cells. + block_funcs::resize_block(*blk_next->data, 0); + delete_element_block(*blk_next); + m_blocks.erase(m_blocks.begin() + block_pos); + } + + adjust_block_positions_func{}(m_blocks, block_pos, -size_to_erase); + } + else + { + // Previous block is empty. + if (blk_next->data) + { + // Next block is not empty. Nothing to do. + adjust_block_positions_func{}(m_blocks, block_pos, -size_to_erase); + return; + } + + // Both blocks are empty. Simply increase the size of the + // previous block. + blk_prev->size += blk_next->size; + delete_element_block(*blk_next); + m_blocks.erase(m_blocks.begin() + block_pos); + adjust_block_positions_func{}(m_blocks, block_pos, -size_to_erase); + } +} + +template +typename multi_type_vector::iterator multi_type_vector::insert_empty(size_type pos, size_type length) +{ + if (!length) + // Nothing to insert. + return end(); + + size_type block_index = get_block_position(pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::insert_empty", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = insert_empty_impl(pos, block_index, length); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in insert_empty (pos=" << pos << "; length=" << length << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +typename multi_type_vector::iterator multi_type_vector::insert_empty( + const iterator& pos_hint, size_type pos, size_type length) +{ + if (!length) + // Nothing to insert. + return end(); + + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::insert_empty", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = insert_empty_impl(pos, block_index, length); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in insert_empty (pos=" << pos << "; length=" << length << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +typename multi_type_vector::iterator multi_type_vector::insert_empty_impl( + size_type pos, size_type block_index, size_type length) +{ + assert(pos < m_cur_size); + + block* blk = &m_blocks[block_index]; + if (!blk->data) + { + // Insertion point is already empty. Just expand its size and be done + // with it. + blk->size += length; + m_cur_size += length; + adjust_block_positions_func{}(m_blocks, block_index + 1, length); + return get_iterator(block_index); + } + + size_type start_pos = blk->position; + + if (start_pos == pos) + { + // Insertion point is at the top of an existing non-empty block. + block* blk_prev = get_previous_block_of_type(block_index, mtv::element_type_empty); + if (blk_prev) + { + assert(!blk_prev->data); + // Previous block is empty. Expand the size of the previous + // block and bail out. + blk_prev->size += length; + m_cur_size += length; + adjust_block_positions_func{}(m_blocks, block_index, length); + return get_iterator(block_index - 1); + } + + // Insert a new empty block. + m_blocks.emplace(m_blocks.begin() + block_index, blk->position, length); + m_cur_size += length; + adjust_block_positions_func{}(m_blocks, block_index + 1, length); + return get_iterator(block_index); + } + + assert(blk->data); + assert(pos > start_pos); + + size_type size_blk_prev = pos - start_pos; + size_type size_blk_next = blk->size - size_blk_prev; + + // Insert two new blocks below the current; one for the empty block being + // inserted, and the other for the lower part of the current non-empty + // block. + m_blocks.insert(m_blocks.begin() + block_index + 1, 2u, block()); + + blk = &m_blocks[block_index]; // The old pointer is invalid. + m_blocks[block_index + 1].size = length; + m_blocks[block_index + 2].size = size_blk_next; + + block* blk_next = &m_blocks[block_index + 2]; + blk_next->data = block_funcs::create_new_block(mdds::mtv::get_block_type(*blk->data), 0); + m_hdl_event.element_block_acquired(blk_next->data); + + // Check if the previous block is the bigger one + if (size_blk_prev > size_blk_next) + { + // Upper (previous) block is larger than the lower (next) block. Copy + // the lower values to the next block. + block_funcs::assign_values_from_block(*blk_next->data, *blk->data, size_blk_prev, size_blk_next); + block_funcs::resize_block(*blk->data, size_blk_prev); + blk->size = size_blk_prev; + } + else + { + // Lower (next) block is larger than the upper (previous) block. Copy + // the upper values to the "next" block. + block_funcs::assign_values_from_block(*blk_next->data, *blk->data, 0, size_blk_prev); + blk_next->size = size_blk_prev; + + // Remove the copied values and push the rest to the top. + block_funcs::erase(*blk->data, 0, size_blk_prev); + + // Set the size of the current block to its new size ( what is after the new block ) + blk->size = size_blk_next; + + // And now let's swap the blocks, but save the block position. + size_type position = blk->position; + blk->swap(*blk_next); + blk->position = position; + } + + m_cur_size += length; + m_blocks[block_index + 1].position = detail::calc_next_block_position(m_blocks, block_index); + m_blocks[block_index + 2].position = detail::calc_next_block_position(m_blocks, block_index + 1); + adjust_block_positions_func{}(m_blocks, block_index + 3, length); + + return get_iterator(block_index + 1); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_impl( + size_type row, size_type end_row, size_type block_index1, const T& it_begin, const T& it_end) +{ + size_type block_index2 = get_block_position(end_row, block_index1); + if (block_index2 == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set_cells_impl", __LINE__, end_row, block_size(), size()); + + if (block_index1 == block_index2) + { + // The whole data array will fit in a single block. + return set_cells_to_single_block(row, end_row, block_index1, it_begin, it_end); + } + + return set_cells_to_multi_blocks(row, end_row, block_index1, block_index2, it_begin, it_end); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::insert_cells_impl( + size_type row, size_type block_index, const T& it_begin, const T& it_end) +{ + size_type start_row = m_blocks[block_index].position; + size_type length = std::distance(it_begin, it_end); + if (!length) + // empty data array. nothing to do. + return end(); + + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + block* blk = &m_blocks[block_index]; + if (!blk->data) + { + if (row == start_row) + { + // Insert into an empty block. Check the previos block (if + // exists) to see if the data can be appended to it if inserting + // at the top of the block. + block* blk0 = get_previous_block_of_type(block_index, cat); + if (blk0) + { + // Append to the previous block. + mdds_mtv_append_values(*blk0->data, *it_begin, it_begin, it_end); + blk0->size += length; + m_cur_size += length; + adjust_block_positions_func{}(m_blocks, block_index, length); + + return get_iterator(block_index - 1); + } + + // Just insert a new block before the current block. + size_type position = m_blocks[block_index].position; + m_blocks.emplace(m_blocks.begin() + block_index, position, length); + blk = &m_blocks[block_index]; + blk->data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk->data); + mdds_mtv_assign_values(*blk->data, *it_begin, it_begin, it_end); + m_cur_size += length; + adjust_block_positions_func{}(m_blocks, block_index + 1, length); + + return get_iterator(block_index); + } + + insert_cells_to_middle(row, block_index, it_begin, it_end); + m_cur_size += length; + + return get_iterator(block_index + 1); + } + + assert(blk->data); + element_category_type blk_cat = mdds::mtv::get_block_type(*blk->data); + if (cat == blk_cat) + { + // Simply insert the new data series into existing block. + assert(it_begin != it_end); + mdds_mtv_insert_values(*blk->data, row - start_row, *it_begin, it_begin, it_end); + blk->size += length; + m_cur_size += length; + adjust_block_positions_func{}(m_blocks, block_index + 1, length); + + return get_iterator(block_index); + } + + assert(cat != blk_cat); + if (row == start_row) + { + // Check the previous block to see if we can append the data there. + block* blk0 = get_previous_block_of_type(block_index, cat); + if (blk0) + { + // Append to the previous block. + mdds_mtv_append_values(*blk0->data, *it_begin, it_begin, it_end); + blk0->size += length; + m_cur_size += length; + adjust_block_positions_func{}(m_blocks, block_index, length); + + return get_iterator(block_index - 1); + } + + // Just insert a new block before the current block. + m_blocks.emplace(m_blocks.begin() + block_index, blk->position, length); + blk = &m_blocks[block_index]; + blk->data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk->data); + mdds_mtv_assign_values(*blk->data, *it_begin, it_begin, it_end); + blk->size = length; + m_cur_size += length; + adjust_block_positions_func{}(m_blocks, block_index + 1, length); + + return get_iterator(block_index); + } + + insert_cells_to_middle(row, block_index, it_begin, it_end); + m_cur_size += length; + + return get_iterator(block_index + 1); +} + +template +template +void multi_type_vector::insert_cells_to_middle( + size_type row, size_type block_index, const T& it_begin, const T& it_end) +{ + size_type start_row = m_blocks[block_index].position; + size_type length = std::distance(it_begin, it_end); + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + + // Insert two new blocks after the specified block position. + size_type n1 = row - start_row; + size_type n2 = m_blocks[block_index].size - n1; + m_blocks.insert(m_blocks.begin() + block_index + 1, 2u, block()); + block* blk = &m_blocks[block_index]; + blk->size = n1; + + m_blocks[block_index + 1].size = length; + m_blocks[block_index + 1].position = detail::calc_next_block_position(*blk); + m_blocks[block_index + 2].size = n2; + m_blocks[block_index + 2].position = detail::calc_next_block_position(m_blocks[block_index + 1]); + + // block for data series. + block* blk2 = &m_blocks[block_index + 1]; + blk2->data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk2->data); + mdds_mtv_assign_values(*blk2->data, *it_begin, it_begin, it_end); + + if (blk->data) + { + element_category_type blk_cat = mdds::mtv::get_block_type(*blk->data); + + // block to hold data from the lower part of the existing block. + block* blk3 = &m_blocks[block_index + 2]; + blk3->data = block_funcs::create_new_block(blk_cat, 0); + m_hdl_event.element_block_acquired(blk3->data); + + // Transfer the lower part of the current block to the new block. + size_type offset = row - start_row; + block_funcs::assign_values_from_block(*blk3->data, *blk->data, offset, n2); + block_funcs::resize_block(*blk->data, blk->size); + } + + adjust_block_positions_func{}(m_blocks, block_index + 3, length); +} + +template +typename multi_type_vector::block& multi_type_vector::set_new_block_to_middle( + size_type block_index, size_type offset, size_type new_block_size, bool overwrite) +{ + assert(block_index < m_blocks.size()); + + // First, insert two new blocks at position past the current block. + size_type lower_block_size = m_blocks[block_index].size - offset - new_block_size; + m_blocks.insert(m_blocks.begin() + block_index + 1, 2u, block()); + m_blocks[block_index + 1].size = new_block_size; // empty block. + m_blocks[block_index + 2].size = lower_block_size; + + block& blk = m_blocks[block_index]; + + if (blk.data) + { + size_type lower_data_start = offset + new_block_size; + block& blk_lower = m_blocks[block_index + 2]; + assert(blk_lower.size == lower_block_size); + element_category_type cat = mtv::get_block_type(*blk.data); + blk_lower.data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk_lower.data); + + // Try to copy the fewer amount of data to the new non-empty block. + if (offset > lower_block_size) + { + // Keep the upper values in the current block and copy the lower + // values to the new non-empty block. + block_funcs::assign_values_from_block(*blk_lower.data, *blk.data, lower_data_start, lower_block_size); + + if (overwrite) + { + // Overwrite cells that will become empty. + block_funcs::overwrite_values(*blk.data, offset, new_block_size); + } + + // Shrink the current data block. + block_funcs::resize_block(*blk.data, offset); + blk.size = offset; + blk_lower.size = lower_block_size; + } + else + { + // Keep the lower values in the current block and copy the upper + // values to the new non-empty block (blk_lower), and swap the two + // later. + block_funcs::assign_values_from_block(*blk_lower.data, *blk.data, 0, offset); + blk_lower.size = offset; + + if (overwrite) + { + // Overwrite cells that will become empty. + block_funcs::overwrite_values(*blk.data, offset, new_block_size); + } + + // Remove the upper and middle values and push the rest to the top. + block_funcs::erase(*blk.data, 0, lower_data_start); + + // Set the size of the current block to its new size ( what is after the new block ) + blk.size = lower_block_size; + blk_lower.size = offset; + + // And now let's swap the blocks, while preserving the position of the original block. + size_type position = blk.position; + blk.swap(blk_lower); + blk.position = position; + } + } + else + { + // There is no data, we just need to update the size of the block + blk.size = offset; + } + + // Re-calculate the block positions. + m_blocks[block_index + 1].position = detail::calc_next_block_position(m_blocks, block_index); + m_blocks[block_index + 2].position = detail::calc_next_block_position(m_blocks, block_index + 1); + + return m_blocks[block_index + 1]; +} + +template +typename multi_type_vector::block* multi_type_vector::get_previous_block_of_type( + size_type block_index, element_category_type cat) +{ + if (block_index == 0) + // No previous block. + return nullptr; + + block& blk = m_blocks[block_index - 1]; + if (blk.data) + return (cat == mtv::get_block_type(*blk.data)) ? &blk : nullptr; + + return (cat == mtv::element_type_empty) ? &blk : nullptr; +} + +template +typename multi_type_vector::block* multi_type_vector::get_next_block_of_type( + size_type block_index, element_category_type cat) +{ + if (block_index == m_blocks.size() - 1) + // No next block. + return nullptr; + + block* blk = &m_blocks[block_index + 1]; + if (blk->data) + return (cat == mtv::get_block_type(*blk->data)) ? blk : nullptr; + + return (cat == mtv::element_type_empty) ? blk : nullptr; +} + +template +typename multi_type_vector::element_block_type* multi_type_vector::exchange_elements( + const element_block_type& src_data, size_type src_offset, size_type dst_index, size_type dst_offset, size_type len) +{ + assert(dst_index < m_blocks.size()); + block* blk = &m_blocks[dst_index]; + element_category_type cat_src = mtv::get_block_type(src_data); + block* blk_next = get_next_block_of_type(dst_index, cat_src); + + if (dst_offset == 0) + { + // Set elements to the top of the destination block. + block* blk_prev = get_previous_block_of_type(dst_index, cat_src); + + if (blk->size == len) + { + // The whole block will get replaced. + std::unique_ptr data(blk->data); + m_hdl_event.element_block_released(blk->data); + blk->data = nullptr; // Prevent its deletion when the parent block gets deleted. + + if (blk_prev) + { + // Append to the previous block. Remove the current block. + block_funcs::append_values_from_block(*blk_prev->data, src_data, src_offset, len); + blk_prev->size += len; + typename blocks_type::iterator it = m_blocks.begin(); + std::advance(it, dst_index); + typename blocks_type::iterator it_end = it; + ++it_end; + + assert(!blk->data); + // no need to call delete_block since mp_data is null. + + if (blk_next) + { + // Apend elements from the next block too. + block_funcs::append_block(*blk_prev->data, *blk_next->data); + blk_prev->size += blk_next->size; + ++it_end; + delete_element_block(*blk_next); + } + + m_blocks.erase(it, it_end); + return data.release(); + } + + // Check the next block to see if we need to merge. + if (blk_next) + { + // We need to merge with the next block. Remove the current + // block and use the next block to store the new elements as + // well as the existing ones. + block_funcs::prepend_values_from_block(*blk_next->data, src_data, src_offset, len); + blk_next->position -= len; + blk_next->size += len; + m_blocks.erase(m_blocks.begin() + dst_index); + } + else + { + blk->data = block_funcs::create_new_block(cat_src, 0); + m_hdl_event.element_block_acquired(blk->data); + assert(blk->data && blk->data != data.get()); + block_funcs::assign_values_from_block(*blk->data, src_data, src_offset, len); + } + + // Return this data block as-is. + return data.release(); + } + + // New block to send back to the caller. + std::unique_ptr data(nullptr); + + if (blk->data) + { + element_category_type cat_dst = mtv::get_block_type(*blk->data); + data.reset(block_funcs::create_new_block(cat_dst, 0)); + + // We need to keep the tail elements of the current block. + block_funcs::assign_values_from_block(*data, *blk->data, 0, len); + block_funcs::erase(*blk->data, 0, len); + } + + size_type position = blk->position; + blk->position += len; + blk->size -= len; + + if (blk_prev) + { + // Append the new elements to the previous block. + block_funcs::append_values_from_block(*blk_prev->data, src_data, src_offset, len); + blk_prev->size += len; + } + else + { + // Insert a new block to house the new elements. + m_blocks.emplace(m_blocks.begin() + dst_index, position, len); + blk = &m_blocks[dst_index]; + blk->data = block_funcs::create_new_block(cat_src, 0); + m_hdl_event.element_block_acquired(blk->data); + block_funcs::assign_values_from_block(*blk->data, src_data, src_offset, len); + } + + return data.release(); + } + + // New block to send back to the caller. + std::unique_ptr data(nullptr); + + if (blk->data) + { + // Copy the elements of the current block to the block being returned. + element_category_type cat_dst = mtv::get_block_type(*blk->data); + data.reset(block_funcs::create_new_block(cat_dst, 0)); + block_funcs::assign_values_from_block(*data, *blk->data, dst_offset, len); + } + + assert(dst_offset > 0); + size_type dst_end_pos = dst_offset + len; + + if (dst_end_pos == blk->size) + { + // The new elements will replace the lower part of the block. + assert(blk->data && "nullptr block mp_data"); + block_funcs::resize_block(*blk->data, dst_offset); + blk->size = dst_offset; + + if (blk_next) + { + // Merge with the next block. + block_funcs::prepend_values_from_block(*blk_next->data, src_data, src_offset, len); + blk_next->position -= len; + blk_next->size += len; + } + else + { + // Insert a new block to store the new elements. + size_type position = blk->position + dst_offset; + m_blocks.emplace(m_blocks.begin() + dst_index + 1, position, len); + blk = &m_blocks[dst_index + 1]; + blk->data = block_funcs::create_new_block(cat_src, 0); + assert(blk->data); + m_hdl_event.element_block_acquired(blk->data); + block_funcs::assign_values_from_block(*blk->data, src_data, src_offset, len); + } + } + else + { + // The new elements will replace the middle of the block. + assert(dst_end_pos < blk->size); + blk = &set_new_block_to_middle(dst_index, dst_offset, len, false); + assert(blk->size == len); + blk->data = block_funcs::create_new_block(cat_src, 0); + assert(blk->data); + m_hdl_event.element_block_acquired(blk->data); + block_funcs::assign_values_from_block(*blk->data, src_data, src_offset, len); + } + + return data.release(); +} + +template +bool multi_type_vector::append_empty(size_type len) +{ + // Append empty cells. + if (m_blocks.empty()) + { + // No existing block. Create a new one. + assert(m_cur_size == 0); + m_blocks.emplace_back(0, len); + m_cur_size = len; + return true; + } + + bool new_block_added = false; + block* blk_last = &m_blocks.back(); + + if (!blk_last->data) + { + // Last block is empty. Just increase its size. + blk_last->size += len; + } + else + { + // Append a new empty block. + m_blocks.emplace_back(m_cur_size, len); + new_block_added = true; + } + + m_cur_size += len; + + return new_block_added; +} + +template +void multi_type_vector::exchange_elements( + const element_block_type& src_data, size_type src_offset, size_type dst_index1, size_type dst_offset1, + size_type dst_index2, size_type dst_offset2, size_type len, blocks_type& new_blocks) +{ + assert(dst_index1 < dst_index2); + assert(dst_offset1 < m_blocks[dst_index1].size); + assert(dst_offset2 < m_blocks[dst_index2].size); + + blocks_to_transfer bucket; + prepare_blocks_to_transfer(bucket, dst_index1, dst_offset1, dst_index2, dst_offset2); + + m_blocks.emplace(m_blocks.begin() + bucket.insert_index, 0, len); + block* blk = &m_blocks[bucket.insert_index]; + if (bucket.insert_index > 0) + blk->position = detail::calc_next_block_position(m_blocks, bucket.insert_index - 1); + + blk->data = block_funcs::create_new_block(mtv::get_block_type(src_data), 0); + m_hdl_event.element_block_acquired(blk->data); + block_funcs::assign_values_from_block(*blk->data, src_data, src_offset, len); + merge_with_adjacent_blocks(bucket.insert_index); + + new_blocks.swap(bucket.blocks); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_to_single_block( + size_type start_row, size_type end_row, size_type block_index, const T& it_begin, const T& it_end) +{ + assert(it_begin != it_end); + assert(!m_blocks.empty()); + + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + block* blk = &m_blocks[block_index]; + size_type start_row_in_block = blk->position; + size_type data_length = std::distance(it_begin, it_end); + + if (blk->data && mdds::mtv::get_block_type(*blk->data) == cat) + { + // simple overwrite. + size_type offset = start_row - start_row_in_block; + block_funcs::overwrite_values(*blk->data, offset, data_length); + if (!offset && data_length == blk->size) + // Overwrite the whole block. It's faster to use assign_values. + mdds_mtv_assign_values(*blk->data, *it_begin, it_begin, it_end); + else + mdds_mtv_set_values(*blk->data, offset, *it_begin, it_begin, it_end); + + return get_iterator(block_index); + } + + size_type end_row_in_block = start_row_in_block + blk->size - 1; + if (start_row == start_row_in_block) + { + if (end_row == end_row_in_block) + { + // Check if we could append it to the previous block. + if (append_to_prev_block(block_index, cat, end_row - start_row + 1, it_begin, it_end)) + { + delete_element_block(*blk); + m_blocks.erase(m_blocks.begin() + block_index); + + // Check if we need to merge it with the next block. + --block_index; + merge_with_next_block(block_index); + return get_iterator(block_index); + } + + // Replace the whole block. + if (blk->data) + { + m_hdl_event.element_block_released(blk->data); + block_funcs::delete_block(blk->data); + } + + blk->data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk->data); + mdds_mtv_assign_values(*blk->data, *it_begin, it_begin, it_end); + merge_with_next_block(block_index); + return get_iterator(block_index); + } + + // Replace the upper part of the block. + + // Shrink the current block first. + size_type length = end_row_in_block - end_row; + blk->size = length; + + if (blk->data) + { + // Erase the upper part of the data from the current data array. + std::unique_ptr new_data( + block_funcs::create_new_block(mdds::mtv::get_block_type(*blk->data), 0)); + + if (!new_data) + throw std::logic_error("failed to instantiate a new data array."); + + size_type pos = end_row - start_row_in_block + 1; + block_funcs::assign_values_from_block(*new_data, *blk->data, pos, length); + block_funcs::overwrite_values(*blk->data, 0, pos); + + // Resize the block to zero before deleting, to prevent the + // managed cells from being deleted when the block is deleted. + block_funcs::resize_block(*blk->data, 0); + block_funcs::delete_block(blk->data); + blk->data = new_data.release(); + + // We don't call element block event listeners here. + } + + length = end_row - start_row + 1; + if (append_to_prev_block(block_index, cat, length, it_begin, it_end)) + { + // The new values have been successfully appended to the previous block. + blk->position += length; + return get_iterator(block_index - 1); + } + + // Insert a new block before the current block, and populate it with + // the new data. + size_type position = blk->position; + blk->position += length; + m_blocks.emplace(m_blocks.begin() + block_index, position, length); + blk = &m_blocks[block_index]; + blk->data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk->data); + blk->size = length; + mdds_mtv_assign_values(*blk->data, *it_begin, it_begin, it_end); + return get_iterator(block_index); + } + + assert(start_row > start_row_in_block); + if (end_row == end_row_in_block) + { + // Shrink the end of the current block and insert a new block for the new data series after the current block. + size_type new_size = start_row - start_row_in_block; + blk->size = new_size; + if (blk->data) + { + block_funcs::overwrite_values(*blk->data, new_size, data_length); + block_funcs::resize_block(*blk->data, new_size); + } + + new_size = end_row - start_row + 1; // size of the data array being inserted. + + if (block_index < m_blocks.size() - 1) + { + // There is a block (or more) after the current block. Check the next block. + block* blk_next = get_next_block_of_type(block_index, cat); + if (blk_next) + { + // Prepend it to the next block. + mdds_mtv_prepend_values(*blk_next->data, *it_begin, it_begin, it_end); + blk_next->size += new_size; + blk_next->position -= new_size; + return get_iterator(block_index + 1); + } + + // Next block has a different data type. Do the normal insertion. + size_type position = detail::calc_next_block_position(*blk); + m_blocks.emplace(m_blocks.begin() + block_index + 1, position, new_size); + blk = &m_blocks[block_index + 1]; + blk->data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk->data); + mdds_mtv_assign_values(*blk->data, *it_begin, it_begin, it_end); + return get_iterator(block_index + 1); + } + + // Last block. + assert(block_index == m_blocks.size() - 1); + + m_blocks.emplace_back(m_cur_size - new_size, new_size); + blk = &m_blocks.back(); + blk->data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk->data); + mdds_mtv_assign_values(*blk->data, *it_begin, it_begin, it_end); + return get_iterator(block_index + 1); + } + + // new data array will be in the middle of the current block. + assert(start_row_in_block < start_row && end_row < end_row_in_block); + + block& blk_new = + set_new_block_to_middle(block_index, start_row - start_row_in_block, end_row - start_row + 1, true); + + blk_new.data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk_new.data); + mdds_mtv_assign_values(*blk_new.data, *it_begin, it_begin, it_end); + + return get_iterator(block_index + 1); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_to_multi_blocks( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end) +{ + assert(block_index1 < block_index2); + assert(it_begin != it_end); + assert(!m_blocks.empty()); + + block* blk1 = &m_blocks[block_index1]; + if (blk1->data) + { + return set_cells_to_multi_blocks_block1_non_empty( + start_row, end_row, block_index1, block_index2, it_begin, it_end); + } + + // Block 1 is empty. + assert(!blk1->data); + + return set_cells_to_multi_blocks_block1_non_equal(start_row, end_row, block_index1, block_index2, it_begin, it_end); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_to_multi_blocks_block1_non_equal( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end) +{ + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + block* blk1 = &m_blocks[block_index1]; + block* blk2 = &m_blocks[block_index2]; + size_type start_row_in_block1 = blk1->position; + size_type start_row_in_block2 = blk2->position; + size_type length = std::distance(it_begin, it_end); + size_type offset = start_row - start_row_in_block1; + size_type end_row_in_block2 = start_row_in_block2 + blk2->size - 1; + + // Initially set to erase blocks between block 1 and block 2 non-inclusive at either end. + typename blocks_type::iterator it_erase_begin = m_blocks.begin() + block_index1 + 1; + typename blocks_type::iterator it_erase_end = m_blocks.begin() + block_index2; + + // Create the new data block first. + block data_blk(start_row, length); + + bool blk0_copied = false; + if (offset == 0) + { + // Remove block 1. + --it_erase_begin; + + // Check the type of the previous block (block 0) if exists. + if (block_index1 > 0) + { + block* blk0 = &m_blocks[block_index1 - 1]; + if (blk0->data && cat == mdds::mtv::get_block_type(*blk0->data)) + { + // Transfer the whole data from block 0 to data block. + data_blk.data = blk0->data; + blk0->data = nullptr; + + data_blk.size += blk0->size; + data_blk.position = blk0->position; + + --it_erase_begin; + blk0_copied = true; + } + } + } + else + { + // Shrink block 1 by the end. + if (blk1->data) + { + size_type n = blk1->size - offset; + block_funcs::overwrite_values(*blk1->data, offset, n); + block_funcs::resize_block(*blk1->data, offset); + } + blk1->size = offset; + } + + if (blk0_copied) + mdds_mtv_append_values(*data_blk.data, *it_begin, it_begin, it_end); + else + { + data_blk.data = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(data_blk.data); + mdds_mtv_assign_values(*data_blk.data, *it_begin, it_begin, it_end); + } + + if (end_row == end_row_in_block2) + { + // Remove block 2. + ++it_erase_end; + + if (block_index2 + 1 < m_blocks.size()) + { + // There is at least one block after block 2. + block* blk3 = &m_blocks[block_index2 + 1]; + if (blk3->data && mdds::mtv::get_block_type(*blk3->data) == cat) + { + // Merge the whole block 3 with the new data. Remove block 3 + // afterward. Resize block 3 to zero to prevent invalid free. + block_funcs::append_block(*data_blk.data, *blk3->data); + block_funcs::resize_block(*blk3->data, 0); + data_blk.size += blk3->size; + ++it_erase_end; + } + } + } + else + { + bool erase_upper = true; + if (blk2->data) + { + element_category_type blk_cat2 = mdds::mtv::get_block_type(*blk2->data); + if (blk_cat2 == cat) + { + // Merge the lower part of block 2 with the new data, and + // erase block 2. Resize block 2 to avoid invalid free on the + // copied portion of the block. + size_type copy_pos = end_row - start_row_in_block2 + 1; + size_type size_to_copy = end_row_in_block2 - end_row; + block_funcs::append_values_from_block(*data_blk.data, *blk2->data, copy_pos, size_to_copy); + block_funcs::resize_block(*blk2->data, copy_pos); + data_blk.size += size_to_copy; + + ++it_erase_end; + erase_upper = false; + } + } + + if (erase_upper) + { + // Erase the upper part of block 2. + size_type size_to_erase = end_row - start_row_in_block2 + 1; + if (blk2->data) + { + block_funcs::overwrite_values(*blk2->data, 0, size_to_erase); + block_funcs::erase(*blk2->data, 0, size_to_erase); + } + blk2->size -= size_to_erase; + blk2->position += size_to_erase; + } + } + + size_type insert_pos = std::distance(m_blocks.begin(), it_erase_begin); + + // Remove the in-between blocks first. + delete_element_blocks(it_erase_begin, it_erase_end); + m_blocks.erase(it_erase_begin, it_erase_end); + + // Insert the new data block. + m_blocks.insert(m_blocks.begin() + insert_pos, std::move(data_blk)); + + return get_iterator(insert_pos); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_to_multi_blocks_block1_non_empty( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end) +{ + size_type start_row_in_block1 = m_blocks[block_index1].position; + size_type start_row_in_block2 = m_blocks[block_index2].position; + + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + block* blk1 = &m_blocks[block_index1]; + assert(blk1->data); + element_category_type blk_cat1 = mdds::mtv::get_block_type(*blk1->data); + + if (blk_cat1 == cat) + { + block* blk2 = &m_blocks[block_index2]; + size_type length = std::distance(it_begin, it_end); + size_type offset = start_row - start_row_in_block1; + size_type end_row_in_block2 = start_row_in_block2 + blk2->size - 1; + + // Initially set to erase blocks between block 1 and block 2 non-inclusive at either end. + typename blocks_type::iterator it_erase_begin = m_blocks.begin() + block_index1 + 1; + typename blocks_type::iterator it_erase_end = m_blocks.begin() + block_index2; + + // Extend the first block to store the new data set. + + // Shrink it first to remove the old values, then append new values. + block_funcs::overwrite_values(*blk1->data, offset, blk1->size - offset); + block_funcs::resize_block(*blk1->data, offset); + mdds_mtv_append_values(*blk1->data, *it_begin, it_begin, it_end); + blk1->size = offset + length; + + if (end_row == end_row_in_block2) + { + // Data overlaps the entire block 2. Erase it. + ++it_erase_end; + } + else if (blk2->data) + { + element_category_type blk_cat2 = mdds::mtv::get_block_type(*blk2->data); + if (blk_cat2 == cat) + { + // Copy the lower (non-overwritten) part of block 2 to block + // 1, and remove the whole block 2. Resize block 2 to zero + // first to prevent the transferred / overwritten cells from + // being deleted on block deletion. + size_type data_length = end_row_in_block2 - end_row; + size_type begin_pos = end_row - start_row_in_block2 + 1; + block_funcs::append_values_from_block(*blk1->data, *blk2->data, begin_pos, data_length); + block_funcs::overwrite_values(*blk2->data, 0, begin_pos); + block_funcs::resize_block(*blk2->data, 0); + blk1->size += data_length; + ++it_erase_end; + } + else + { + // Erase the upper part of block 2. + size_type size_to_erase = end_row - start_row_in_block2 + 1; + block_funcs::erase(*blk2->data, 0, size_to_erase); + blk2->size -= size_to_erase; + blk2->position += size_to_erase; + } + } + else + { + // Last block is empty. + size_type size_to_erase = end_row - start_row_in_block2 + 1; + blk2->size -= size_to_erase; + blk2->position += size_to_erase; + } + + delete_element_blocks(it_erase_begin, it_erase_end); + m_blocks.erase(it_erase_begin, it_erase_end); + + return get_iterator(block_index1); + } + + // The first block type is different. + assert(blk_cat1 != cat); + + return set_cells_to_multi_blocks_block1_non_equal(start_row, end_row, block_index1, block_index2, it_begin, it_end); +} + +template +typename multi_type_vector::size_type multi_type_vector::merge_with_adjacent_blocks( + size_type block_index) +{ + assert(!m_blocks.empty()); + assert(block_index < m_blocks.size()); + block* blk_prev = block_index > 0 ? &m_blocks[block_index - 1] : nullptr; + + if (!blk_prev) + { + // No previous block. + merge_with_next_block(block_index); + return 0; + } + + size_type size_prev = blk_prev->size; // size of previous block. + block* blk = &m_blocks[block_index]; + block* blk_next = block_index < (m_blocks.size() - 1) ? &m_blocks[block_index + 1] : nullptr; + + // Check the previous block. + if (blk_prev->data) + { + // Previous block has data. + element_category_type cat_prev = mtv::get_block_type(*blk_prev->data); + if (!blk->data || cat_prev != mtv::get_block_type(*blk->data)) + { + // Current block is empty or is of different type from the previous one. + merge_with_next_block(block_index); + return 0; + } + + // Previous and current blocks are of the same type. + if (blk_next && blk_next->data && cat_prev == get_block_type(*blk_next->data)) + { + // Merge all three blocks. + blk_prev->size += blk->size + blk_next->size; + block_funcs::append_block(*blk_prev->data, *blk->data); + block_funcs::append_block(*blk_prev->data, *blk_next->data); + // Avoid overwriting the transferred elements. + block_funcs::resize_block(*blk->data, 0); + block_funcs::resize_block(*blk_next->data, 0); + + delete_element_block(*blk); + delete_element_block(*blk_next); + + typename blocks_type::iterator it = m_blocks.begin(); + std::advance(it, block_index); + typename blocks_type::iterator it_end = it; + std::advance(it_end, 2); + m_blocks.erase(it, it_end); + return size_prev; + } + + // Merge only the previous and current blocks. + bool merged = merge_with_next_block(block_index - 1); + if (!merged) + assert(!"Blocks were not merged!"); + + return size_prev; + } + + assert(!blk_prev->data); // Previous block is empty. + + if (blk->data) + { + // Current block is not empty. Check with the next block. + merge_with_next_block(block_index); + return 0; + } + + // Previous and current blocks are both empty. + assert(!blk->data); + + if (blk_next && !blk_next->data) + { + // Next block is empty too. Merge all three. + blk_prev->size += blk->size + blk_next->size; + // No need to call delete_element_block() since we know both blocks are empty. + + typename blocks_type::iterator it = m_blocks.begin(); + std::advance(it, block_index); + typename blocks_type::iterator it_end = it; + std::advance(it_end, 2); + m_blocks.erase(it, it_end); + return size_prev; + } + + // Next block is not empty, or does not exist. Merge the current block with the previous one. + bool merged = merge_with_next_block(block_index - 1); + if (!merged) + assert(!"Blocks were not merged!"); + + return size_prev; +} + +template +bool multi_type_vector::merge_with_next_block(size_type block_index) +{ + assert(!m_blocks.empty()); + assert(block_index < m_blocks.size()); + + if (block_index >= m_blocks.size() - 1) + // No more block below this one. + return false; + + // Block exists below. + block* blk = &m_blocks[block_index]; + block* blk_next = &m_blocks[block_index + 1]; + if (!blk->data) + { + // Empty block. Merge only if the next block is also empty. + if (blk_next->data) + // Next block is not empty. + return false; + + // Merge the two blocks. + blk->size += blk_next->size; + m_blocks.erase(m_blocks.begin() + block_index + 1); + return true; + } + + if (!blk_next->data) + return false; + + if (mdds::mtv::get_block_type(*blk->data) != mdds::mtv::get_block_type(*blk_next->data)) + // Block types differ. Don't merge. + return false; + + // Merge it with the next block. + block_funcs::append_block(*blk->data, *blk_next->data); + block_funcs::resize_block(*blk_next->data, 0); + blk->size += blk_next->size; + delete_element_block(*blk_next); + m_blocks.erase(m_blocks.begin() + block_index + 1); + return true; +} + +template +template +bool multi_type_vector::append_to_prev_block( + size_type block_index, element_category_type cat, size_type length, const T& it_begin, const T& it_end) +{ + block* blk_prev = get_previous_block_of_type(block_index, cat); + if (!blk_prev) + return false; + + // Append to the previous block. + mdds_mtv_append_values(*blk_prev->data, *it_begin, it_begin, it_end); + blk_prev->size += length; + return true; +} + +template +void multi_type_vector::clear() +{ + delete_element_blocks(m_blocks.begin(), m_blocks.end()); + m_blocks.clear(); + m_cur_size = 0; +} + +template +typename multi_type_vector::size_type multi_type_vector::size() const +{ + return m_cur_size; +} + +template +typename multi_type_vector::size_type multi_type_vector::block_size() const +{ + return m_blocks.size(); +} + +template +bool multi_type_vector::empty() const +{ + return m_blocks.empty(); +} + +template +void multi_type_vector::resize(size_type new_size) +{ +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + resize_impl(new_size); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in resize (new-size=" << new_size << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +void multi_type_vector::resize_impl(size_type new_size) +{ + if (new_size == m_cur_size) + return; + + if (!new_size) + { + clear(); + return; + } + + if (new_size > m_cur_size) + { + // Append empty cells. + append_empty(new_size - m_cur_size); + return; + } + + assert(new_size < m_cur_size && new_size > 0); + + // Find out in which block the new end row will be. + size_type new_end_row = new_size - 1; + size_type block_index = get_block_position(new_end_row); + if (block_index == m_blocks.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::resize", __LINE__, new_end_row, block_size(), size()); + + block* blk = &m_blocks[block_index]; + size_type start_row_in_block = blk->position; + size_type end_row_in_block = start_row_in_block + blk->size - 1; + + if (new_end_row < end_row_in_block) + { + // Shrink the size of the current block. + size_type new_block_size = new_end_row - start_row_in_block + 1; + if (blk->data) + { + block_funcs::overwrite_values(*blk->data, new_end_row + 1, end_row_in_block - new_end_row); + block_funcs::resize_block(*blk->data, new_block_size); + } + blk->size = new_block_size; + } + + // Remove all blocks that are below this one. + typename blocks_type::iterator it = m_blocks.begin() + block_index + 1; + delete_element_blocks(it, m_blocks.end()); + m_blocks.erase(it, m_blocks.end()); + m_cur_size = new_size; +} + +template +void multi_type_vector::swap(multi_type_vector& other) +{ + std::swap(m_hdl_event, other.m_hdl_event); + std::swap(m_cur_size, other.m_cur_size); + m_blocks.swap(other.m_blocks); +} + +template +void multi_type_vector::swap( + size_type start_pos, size_type end_pos, multi_type_vector& other, size_type other_pos) +{ + if (start_pos > end_pos) + throw std::out_of_range("multi_type_vector::swap: start position is larger than the end position!"); + + size_type other_end_pos = other_pos + end_pos - start_pos; + + if (end_pos >= m_cur_size || other_end_pos >= other.m_cur_size) + throw std::out_of_range("multi_type_vector::swap: end position is out of bound!"); + + size_type block_index1 = get_block_position(start_pos); + if (block_index1 == m_blocks.size()) + throw std::out_of_range("multi_type_vector::swap: start block position in source not found!"); + + size_type block_index2 = get_block_position(end_pos, block_index1); + if (block_index2 == m_blocks.size()) + throw std::out_of_range("multi_type_vector::swap: end block position in source not found!"); + + size_type dest_block_index1 = other.get_block_position(other_pos); + if (dest_block_index1 == other.m_blocks.size()) + throw std::out_of_range("multi_type_vector::swap: start block position in destination not found!"); + + size_type dest_block_index2 = other.get_block_position(other_end_pos, dest_block_index1); + if (dest_block_index2 == other.m_blocks.size()) + throw std::out_of_range("multi_type_vector::swap: end block position in destination not found!"); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block, os_prev_block_other; + dump_blocks(os_prev_block); + other.dump_blocks(os_prev_block_other); +#endif + + swap_impl(other, start_pos, end_pos, other_pos, block_index1, block_index2, dest_block_index1, dest_block_index2); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_block, os_block_other; + dump_blocks(os_block); + other.dump_blocks(os_block_other); + + try + { + check_block_integrity(); + other.check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << std::endl + << "block integrity check failed in swap (start_pos=" << start_pos << "; end_pos=" << end_pos + << "; other_pos=" << other_pos << ")" << std::endl; + os << std::endl << "previous block state (source):" << std::endl; + os << os_prev_block.str(); + os << std::endl << "previous block state (destination):" << std::endl; + os << os_prev_block_other.str(); + os << std::endl << "altered block state (source):" << std::endl; + os << os_block.str(); + os << std::endl << "altered block state (destination):" << std::endl; + os << os_block_other.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +void multi_type_vector::shrink_to_fit() +{ + typename blocks_type::iterator it = m_blocks.begin(), it_end = m_blocks.end(); + for (; it != it_end; ++it) + { + block* blk = &(*it); + assert(blk); + if (blk->data) + block_funcs::shrink_to_fit(*blk->data); + } +} + +template +bool multi_type_vector::operator==(const multi_type_vector& other) const +{ + if (this == &other) + // Comparing to self is always equal. + return true; + + if (m_blocks.size() != other.m_blocks.size()) + // Block sizes differ. + return false; + + if (m_cur_size != other.m_cur_size) + // Row sizes differ. + return false; + + typename blocks_type::const_iterator it = m_blocks.begin(), it_end = m_blocks.end(); + typename blocks_type::const_iterator it2 = other.m_blocks.begin(); + for (; it != it_end; ++it, ++it2) + { + const block* blk1 = &(*it); + const block* blk2 = &(*it2); + + if (blk1->size != blk2->size) + // Block sizes differ. + return false; + + if (blk1->data) + { + if (!blk2->data) + // left is non-empty while right is empty. + return false; + } + else + { + if (blk2->data) + // left is empty while right is non-empty. + return false; + } + + if (!blk1->data) + { + assert(!blk2->data); + continue; + } + + assert(blk1->data && blk2->data); + if (!block_funcs::equal_block(*blk1->data, *blk2->data)) + return false; + } + + return true; +} + +template +bool multi_type_vector::operator!=(const multi_type_vector& other) const +{ + return !operator==(other); +} + +template +multi_type_vector& multi_type_vector::operator=(const multi_type_vector& other) +{ + multi_type_vector assigned(other); + swap(assigned); + return *this; +} + +template +multi_type_vector& multi_type_vector::operator=(multi_type_vector&& other) +{ + multi_type_vector assigned(std::move(other)); + swap(assigned); + return *this; +} + +template +template +mtv::element_t multi_type_vector::get_element_type(const T& elem) +{ + return mdds_mtv_get_element_type(elem); +} + +template +typename multi_type_vector::iterator multi_type_vector::set_whole_block_empty( + size_type block_index, bool overwrite) +{ + block* blk = &m_blocks[block_index]; + if (!overwrite) + // Resize block to 0 before deleting, to prevent its elements from getting deleted. + block_funcs::resize_block(*blk->data, 0); + + delete_element_block(*blk); + + block* blk_prev = get_previous_block_of_type(block_index, mtv::element_type_empty); + block* blk_next = get_next_block_of_type(block_index, mtv::element_type_empty); + + // Merge with adjacent block(s) if necessary. + if (blk_prev) + { + assert(!blk_prev->data); + + if (blk_next) + { + // Both preceding and next blocks are empty. + assert(!blk_next->data); + + blk_prev->size += blk->size + blk_next->size; + + // no need to call delete_block() on the current and next blocks + // since they are empty. + + typename blocks_type::iterator it = m_blocks.begin(); + std::advance(it, block_index); + typename blocks_type::iterator it_end = it; + std::advance(it_end, 2); + m_blocks.erase(it, it_end); + + return get_iterator(block_index - 1); + } + + // Only the preceding block is empty. Merge the current block with the previous. + blk_prev->size += blk->size; + typename blocks_type::iterator it = m_blocks.begin(); + std::advance(it, block_index); + m_blocks.erase(it); + + return get_iterator(block_index - 1); + } + else if (blk_next) + { + assert(!blk_next->data); + + // Only the next block is empty. Merge the next block with the current. + blk->size += blk_next->size; + typename blocks_type::iterator it = m_blocks.begin(); + std::advance(it, block_index + 1); + m_blocks.erase(it); + + return get_iterator(block_index); + } + + return get_iterator(block_index); +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty_in_single_block( + size_type start_row, size_type end_row, size_type block_index, bool overwrite) +{ + // Range is within a single block. + block* blk = &m_blocks[block_index]; + if (!blk->data) + // This block is already empty. Do nothing. + return get_iterator(block_index); + + size_type start_row_in_block = blk->position; + assert(start_row_in_block + blk->size >= 1); + size_type end_row_in_block = start_row_in_block + blk->size - 1; + size_type empty_block_size = end_row - start_row + 1; + + if (start_row == start_row_in_block) + { + // start row coincides with the start of a block. + + if (end_row == end_row_in_block) + return set_whole_block_empty(block_index, overwrite); + + // Set the upper part of the block empty. + if (overwrite) + block_funcs::overwrite_values(*blk->data, 0, empty_block_size); + block_funcs::erase(*blk->data, 0, empty_block_size); + blk->size -= empty_block_size; + + // Check if the preceding block (if exists) is also empty. + block* blk_prev = get_previous_block_of_type(block_index, mtv::element_type_empty); + if (blk_prev) + { + // Extend the previous empty block. + blk_prev->size += empty_block_size; + blk->position += empty_block_size; + return get_iterator(block_index - 1); + } + + // Insert a new empty block before the current one. + size_type block_position = blk->position; + blk->position += empty_block_size; + m_blocks.emplace(m_blocks.begin() + block_index, block_position, empty_block_size); + return get_iterator(block_index); + } + + if (end_row == end_row_in_block) + { + // end row equals the end of a block. + assert(start_row > start_row_in_block); + + // Set the lower part of the block empty. + size_type start_pos = start_row - start_row_in_block; + if (overwrite) + block_funcs::overwrite_values(*blk->data, start_pos, empty_block_size); + block_funcs::erase(*blk->data, start_pos, empty_block_size); + blk->size -= empty_block_size; + + // Check if the following block (if exists) is also empty. + block* blk_next = get_next_block_of_type(block_index, mtv::element_type_empty); + if (blk_next) + { + // Extend the next empty block to cover the new empty segment. + blk_next->size += empty_block_size; + blk_next->position = start_row; + } + else + { + // Insert a new empty block after the current one. + m_blocks.emplace(m_blocks.begin() + block_index + 1, start_row, empty_block_size); + } + + return get_iterator(block_index + 1); + } + + // Empty the middle part of a block. + assert(end_row_in_block - end_row > 0); + set_new_block_to_middle(block_index, start_row - start_row_in_block, empty_block_size, overwrite); + + return get_iterator(block_index + 1); +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty_in_multi_blocks( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, bool overwrite) +{ + assert(block_index1 < block_index2); + size_type start_row_in_block1 = m_blocks[block_index1].position; + size_type start_row_in_block2 = m_blocks[block_index2].position; + + { + // Empty the lower part of the first block. + block* blk = &m_blocks[block_index1]; + if (blk->data) + { + if (start_row_in_block1 == start_row) + { + // Empty the whole block. + + // Check if the previos block (if exists) is also empty. + block* blk_prev = nullptr; + if (block_index1 > 0) + { + blk_prev = &m_blocks[block_index1 - 1]; + if (blk_prev->data) + // Not empty. Ignore it. + blk_prev = nullptr; + } + + if (blk_prev) + { + // Previous block is empty. Move the start row to the + // first row of the previous block, and make the previous + // block 'block 1'. + start_row -= blk_prev->size; + --block_index1; + } + else + { + // Make block 1 empty. + if (!overwrite) + block_funcs::resize_block(*blk->data, 0); + + delete_element_block(*blk); + } + } + else + { + // Empty the lower part. + size_type new_size = start_row - start_row_in_block1; + if (overwrite) + block_funcs::overwrite_values(*blk->data, new_size, blk->size - new_size); + + block_funcs::resize_block(*blk->data, new_size); + blk->size = new_size; + } + } + else + { + // First block is already empty. Adjust the start row of the new + // empty range. + start_row = start_row_in_block1; + } + } + + size_type end_block_to_erase = block_index2; // End block position is non-inclusive. + + { + // Empty the upper part of the last block. + block* blk = &m_blocks[block_index2]; + size_type last_row_in_block = start_row_in_block2 + blk->size - 1; + if (blk->data) + { + if (last_row_in_block == end_row) + { + // Delete the whole block. + ++end_block_to_erase; + + // Check if the following block (if exists) is also empty. + block* blk_next = nullptr; + if (block_index2 + 1 < m_blocks.size()) + { + blk_next = &m_blocks[block_index2 + 1]; + if (blk_next->data) + // Not empty. Ignore it. + blk_next = nullptr; + } + + if (blk_next) + { + // The following block is also empty. + end_row += blk_next->size; + ++end_block_to_erase; + } + } + else + { + // Empty the upper part. + size_type size_to_erase = end_row - start_row_in_block2 + 1; + if (overwrite) + block_funcs::overwrite_values(*blk->data, 0, size_to_erase); + + block_funcs::erase(*blk->data, 0, size_to_erase); + blk->size -= size_to_erase; + blk->position = start_row_in_block2 + size_to_erase; + } + } + else + { + // Last block is empty. Delete this block and adjust the end row + // of the new empty range. + ++end_block_to_erase; + end_row = last_row_in_block; + } + } + + if (end_block_to_erase - block_index1 > 1) + { + // Remove all blocks in-between, from block_index1+1 to end_block_to_erase-1. + + for (size_type i = block_index1 + 1; i < end_block_to_erase; ++i) + { + block& blk = m_blocks[i]; + if (!overwrite && blk.data) + block_funcs::resize_block(*blk.data, 0); + + delete_element_block(blk); + } + + typename blocks_type::iterator it = m_blocks.begin() + block_index1 + 1; + typename blocks_type::iterator it_end = m_blocks.begin() + end_block_to_erase; + m_blocks.erase(it, it_end); + } + + block* blk = &m_blocks[block_index1]; + size_type empty_block_size = end_row - start_row + 1; + if (blk->data) + { + // Insert a new empty block after the first block. + m_blocks.emplace(m_blocks.begin() + block_index1 + 1, start_row, empty_block_size); + return get_iterator(block_index1 + 1); + } + + // Current block is already empty. Just extend its size. + blk->size = empty_block_size; + blk->position = start_row; + return get_iterator(block_index1); +} + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG +template +void multi_type_vector::dump_blocks(std::ostream& os) const +{ + os << "--- blocks" << endl; + for (size_type i = 0, n = m_blocks.size(); i < n; ++i) + { + const block* blk = &m_blocks[i]; + element_category_type cat = mtv::element_type_empty; + if (blk->data) + cat = mtv::get_block_type(*blk->data); + os << " block " << i << ": position=" << blk->position << " size=" << blk->size << " type=" << cat << endl; + } +} + +template +void multi_type_vector::check_block_integrity() const +{ + if (m_blocks.empty()) + // Nothing to check. + return; + + if (m_blocks.size() == 1 && m_blocks[0].size == 0) + throw mdds::integrity_error("block should never be zero sized!"); + + const block* blk_prev = m_blocks.data(); + if (blk_prev->size == 0) + throw mdds::integrity_error("block should never be zero sized!"); + + if (blk_prev->position != 0) + { + std::ostringstream os; + os << "position of the first block should be zero!" << std::endl; + + dump_blocks(os); + mdds::integrity_error(os.str()); + } + + element_category_type cat_prev = mtv::element_type_empty; + if (blk_prev->data) + cat_prev = mtv::get_block_type(*blk_prev->data); + + size_type cur_position = blk_prev->size; + size_type total_size = blk_prev->size; + + for (size_type i = 1, n = m_blocks.size(); i < n; ++i) + { + const block* blk = &m_blocks[i]; + if (blk->size == 0) + throw mdds::integrity_error("block should never be zero sized!"); + + if (blk->position != cur_position) + { + std::ostringstream os; + os << "position of the current block is wrong! (expected=" << cur_position << "; actual=" << blk->position + << ")" << std::endl; + + dump_blocks(os); + throw mdds::integrity_error(os.str()); + } + + element_category_type cat = mtv::element_type_empty; + if (blk->data) + { + cat = mtv::get_block_type(*blk->data); + + if (block_funcs::size(*blk->data) != blk->size) + throw mdds::integrity_error("block size cache and the actual element block size differ!"); + } + + if (cat_prev == cat) + { + std::ostringstream os; + os << "Two adjacent blocks should never be of the same type." << std::endl; + dump_blocks(os); + mdds::integrity_error(os.str()); + } + + blk_prev = blk; + cat_prev = cat; + + total_size += blk->size; + cur_position += blk->size; + } + + if (total_size != m_cur_size) + { + std::ostringstream os; + os << "Current size does not equal the total sizes of all blocks." << std::endl; + os << "current size=" << m_cur_size << " total block size=" << total_size << std::endl; + dump_blocks(os); + mdds::integrity_error(os.str()); + } +} +#endif + +}}} // namespace mdds::mtv::aos + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/block_funcs.hpp b/include/mdds/multi_type_vector/block_funcs.hpp new file mode 100644 index 0000000..7839761 --- /dev/null +++ b/include/mdds/multi_type_vector/block_funcs.hpp @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2022 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#pragma once + +#include "./types.hpp" +#include "../global.hpp" + +#include +#include +#include + +namespace mdds { namespace mtv { + +namespace detail { + +inline void throw_unknown_block(const char* func, const mdds::mtv::element_t type) +{ + std::ostringstream os; + os << func << ": failed to map to a element block function (type=" << type << ")"; + throw general_error(os.str()); +} + +template +auto& find_func( + const std::unordered_map>& func_map, element_t type, + const char* src_func_name) +{ + auto it = func_map.find(type); + if (it == func_map.end()) + detail::throw_unknown_block(src_func_name, type); + + return it->second; +} + +} // namespace detail + +template +struct element_block_funcs +{ + static base_element_block* create_new_block(element_t type, std::size_t init_size) + { + static const std::unordered_map> func_map{ + {Ts::block_type, Ts::create_block}...}; + + auto& f = detail::find_func(func_map, type, __func__); + return f(init_size); + } + + static base_element_block* clone_block(const base_element_block& block) + { + static const std::unordered_map> + func_map{{Ts::block_type, Ts::clone_block}...}; + + auto& f = detail::find_func(func_map, get_block_type(block), __func__); + return f(block); + } + + static void delete_block(const base_element_block* p) + { + if (!p) + return; + + static const std::unordered_map> func_map{ + {Ts::block_type, Ts::delete_block}...}; + + // TODO: We should not throw an exception here as this gets called + // from a destructor and destructors should not throw exceptions. + auto& f = detail::find_func(func_map, get_block_type(*p), __func__); + f(p); + } + + static void resize_block(base_element_block& block, std::size_t new_size) + { + static const std::unordered_map> func_map{ + {Ts::block_type, Ts::resize_block}...}; + + auto& f = detail::find_func(func_map, get_block_type(block), __func__); + f(block, new_size); + } + + static void print_block(const base_element_block& block) + { + static const std::unordered_map> func_map{ + {Ts::block_type, Ts::print_block}...}; + + auto& f = detail::find_func(func_map, get_block_type(block), __func__); + f(block); + } + + static void erase(base_element_block& block, std::size_t pos) + { + static const std::unordered_map> func_map{ + {Ts::block_type, Ts::erase_value}...}; + + auto& f = detail::find_func(func_map, get_block_type(block), __func__); + f(block, pos); + } + + static void erase(base_element_block& block, std::size_t pos, std::size_t size) + { + static const std::unordered_map> + func_map{{Ts::block_type, Ts::erase_values}...}; + + auto& f = detail::find_func(func_map, get_block_type(block), __func__); + f(block, pos, size); + } + + static void append_block(base_element_block& dest, const base_element_block& src) + { + static const std::unordered_map> + func_map{{Ts::block_type, Ts::append_block}...}; + + auto& f = detail::find_func(func_map, get_block_type(dest), __func__); + f(dest, src); + } + + static void append_values_from_block( + base_element_block& dest, const base_element_block& src, std::size_t begin_pos, std::size_t len) + { + using func_type = std::function; + static const std::unordered_map func_map{ + {Ts::block_type, Ts::append_values_from_block}...}; + + auto& f = detail::find_func(func_map, get_block_type(dest), __func__); + f(dest, src, begin_pos, len); + } + + static void assign_values_from_block( + base_element_block& dest, const base_element_block& src, std::size_t begin_pos, std::size_t len) + { + using func_type = std::function; + static const std::unordered_map func_map{ + {Ts::block_type, Ts::assign_values_from_block}...}; + + auto& f = detail::find_func(func_map, get_block_type(dest), __func__); + f(dest, src, begin_pos, len); + } + + static void prepend_values_from_block( + base_element_block& dest, const base_element_block& src, std::size_t begin_pos, std::size_t len) + { + using func_type = std::function; + static const std::unordered_map func_map{ + {Ts::block_type, Ts::prepend_values_from_block}...}; + + auto& f = detail::find_func(func_map, get_block_type(dest), __func__); + f(dest, src, begin_pos, len); + } + + static void swap_values( + base_element_block& blk1, base_element_block& blk2, std::size_t pos1, std::size_t pos2, std::size_t len) + { + element_t blk1_type = get_block_type(blk1); + assert(blk1_type == get_block_type(blk2)); + + using func_type = + std::function; + static const std::unordered_map func_map{{Ts::block_type, Ts::swap_values}...}; + + auto& f = detail::find_func(func_map, blk1_type, __func__); + f(blk1, blk2, pos1, pos2, len); + } + + static bool equal_block(const base_element_block& left, const base_element_block& right) + { + element_t block_type = get_block_type(left); + if (block_type != get_block_type(right)) + return false; + + using func_type = std::function; + static const std::unordered_map func_map{{Ts::block_type, Ts::equal_block}...}; + + auto& f = detail::find_func(func_map, block_type, __func__); + return f(left, right); + } + + static void overwrite_values(base_element_block& block, std::size_t pos, std::size_t len) + { + using func_type = std::function; + static const std::unordered_map func_map{{Ts::block_type, Ts::overwrite_values}...}; + + auto& f = detail::find_func(func_map, get_block_type(block), __func__); + f(block, pos, len); + } + + static void shrink_to_fit(base_element_block& block) + { + using func_type = std::function; + static const std::unordered_map func_map{{Ts::block_type, Ts::shrink_to_fit}...}; + + auto& f = detail::find_func(func_map, get_block_type(block), __func__); + f(block); + } + + static std::size_t size(const base_element_block& block) + { + using func_type = std::function; + static const std::unordered_map func_map{{Ts::block_type, Ts::size}...}; + + auto& f = detail::find_func(func_map, get_block_type(block), __func__); + return f(block); + } +}; + +}} // namespace mdds::mtv + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/collection.hpp b/include/mdds/multi_type_vector/collection.hpp new file mode 100644 index 0000000..73467de --- /dev/null +++ b/include/mdds/multi_type_vector/collection.hpp @@ -0,0 +1,280 @@ +/************************************************************************* + * + * Copyright (c) 2016 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_COLLECTION_HPP +#define INCLUDED_MDDS_COLLECTION_HPP + +#include "mdds/multi_type_vector/types.hpp" + +#include +#include +#include + +namespace mdds { namespace mtv { + +template +class collection; + +namespace detail { + +template +class side_iterator +{ + typedef _MtvT mtv_type; + friend collection; + + typedef typename mtv_type::size_type size_type; + typedef typename mtv_type::const_iterator const_iterator; + typedef typename mtv_type::const_position_type const_position_type; + + /** meta-data about each mtv instance. */ + struct mtv_item + { + const mtv_type* vector; + const_iterator block_pos; + const_iterator block_end; + + mtv_item(const mtv_type* v, const const_iterator& bp, const const_iterator& be) + : vector(v), block_pos(bp), block_end(be) + {} + }; + + /** single element value. */ + struct node + { + friend class side_iterator; + + /** type of current element */ + mdds::mtv::element_t type; + + /** index of current mtv instance */ + size_type index; + + /** logical position of the current element within the mtv. */ + size_type position; + + template + typename _Blk::value_type get() const + { + return _Blk::get_value(*__position.first->data, __position.second); + } + + private: + /** position object of current element within the mtv instance. */ + const_position_type __position; + }; + + enum begin_state_type + { + begin_state + }; + enum end_state_type + { + end_state + }; + + std::vector m_vectors; + node m_cur_node; + size_type m_elem_pos; + size_type m_elem_pos_end; + size_type m_index_offset; + uintptr_t m_identity; + + side_iterator( + std::vector&& vectors, size_type elem_pos, size_type elem_size, size_type index_offset, + uintptr_t identity, begin_state_type); + + side_iterator( + std::vector&& vectors, size_type elem_pos, size_type elem_size, size_type index_offset, + uintptr_t identity, end_state_type); + +public: + typedef node value_type; + + side_iterator(); + + template + side_iterator(const _T& begin, const _T& end); + + const value_type& operator*() const + { + return m_cur_node; + } + + const value_type* operator->() const + { + return &m_cur_node; + } + + side_iterator& operator++(); + + side_iterator operator++(int); + + bool operator==(const side_iterator& other) const; + bool operator!=(const side_iterator& other) const; +}; + +} // namespace detail + +/** + * Special-purpose collection of multiple multi_type_vector instances to + * allow them to be traversed "sideways" i.e. orthogonal to the direction of + * the vector instances. All involved multi_type_vector instances must be of + * the same type and length. + */ +template +class collection +{ +public: + typedef _MtvT mtv_type; + typedef typename mtv_type::size_type size_type; + +private: + struct range + { + size_type start; + size_type size; + + range() : start(0), size(0) + {} + }; + + std::vector m_vectors; + size_type m_mtv_size; + uintptr_t m_identity; + + range m_elem_range; /// element range. + range m_col_range; /// collection range. + +public: + typedef detail::side_iterator const_iterator; + + collection(); + + /** + * Constructor that takes the start and end iterators of the + * multi_type_vector instances to reference in the collection. + * + * @param begin iterator that references the first multi_type_vector + * instance to place in the collection. + * @param end iterator that references the position past the last + * multi_type_vector instance to place in the collection. + */ + template + collection(const _T& begin, const _T& end); + + /** + * Return an iterator that references the first element in the + * collection. + * + * @return iterator that references the first element in the collection. + */ + const_iterator begin() const; + + /** + * Return an iterator that references the position past the last element + * in the collection. + * + * @return iterator that references the position past the last element in + * the collection. + */ + const_iterator end() const; + + /** + * Return the length of the vector instances stored in the collection. + * This will be equivalent of the length of each multi_type_vector + * instance, since all stored instances have the same length. + * + * @return length of the stored multi_type_vector instances. + */ + size_type size() const; + + /** + * Swap the entire collection with another collection instance. + * + * @param other another collection instance to swap contents with. + */ + void swap(collection& other); + + /** + * Set the sub-range of the collection to iterate through. + * + *

For instance, if the collection consists of 100 multi_type_vector + * instances, and you want to iterate through only 50 of them starting + * from the second instance, you set the start index to 1 (as it's + * 0-based), and the size to 50.

+ * + * @param start 0-based index of the first multi_type_vector instance to + * iterate through. + * @param size length of the collection range i.e. the number of vector + * instances to iterate through starting from the specified + * first vector instance. + */ + void set_collection_range(size_type start, size_type size); + + /** + * Set the sub element range to iterate through. This limits the element + * range in each multi_type_vector instance to iterate through. The + * direction of the element range is orthogonal to the direction of the + * collection range. + * + *

For instance, if the collection consists of multiple + * multi_type_vector instances all of which have a length of 50, and you + * only wish to iterate from the 3rd element through the 10th element in + * each vector instance, then you set the start index to 2 and the + * size to 8.

+ * + * @param start 0-based index of the starting element position. + * @param size length of the element range to iterate through starting + * from the specified start element position. + */ + void set_element_range(size_type start, size_type size); + +private: + void check_collection_range(size_type start, size_type size) const; + void check_element_range(size_type start, size_type size) const; + + std::vector build_iterator_state() const; + + void init_insert_vector(const std::unique_ptr& p); + + void init_insert_vector(const std::shared_ptr& p); + + template + void init_insert_vector(const _T& t, typename std::enable_if::value>::type* = 0); + + template + void init_insert_vector(const _T& t, typename std::enable_if::value>::type* = 0); + + void check_vector_size(const mtv_type& t); +}; + +}} // namespace mdds::mtv + +#include "collection_def.inl" + +#endif diff --git a/include/mdds/multi_type_vector/collection_def.inl b/include/mdds/multi_type_vector/collection_def.inl new file mode 100644 index 0000000..6a1a0d2 --- /dev/null +++ b/include/mdds/multi_type_vector/collection_def.inl @@ -0,0 +1,305 @@ +/************************************************************************* + * + * Copyright (c) 2016-2018 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#include + +namespace mdds { namespace mtv { + +namespace detail { + +template +side_iterator<_MtvT>::side_iterator() : m_elem_pos(0), m_elem_pos_end(0), m_index_offset(0), m_identity(0) +{} + +template +side_iterator<_MtvT>::side_iterator( + std::vector&& vectors, size_type elem_pos, size_type elem_size, size_type index_offset, + uintptr_t identity, begin_state_type) + : m_vectors(std::move(vectors)), m_elem_pos(elem_pos), m_elem_pos_end(elem_pos + elem_size), + m_index_offset(index_offset), m_identity(identity) +{ + m_cur_node.index = index_offset; + + if (m_vectors.empty()) + return; + + mtv_item& col1 = m_vectors.front(); + + m_cur_node.__position = col1.vector->position(col1.block_pos, m_elem_pos); + col1.block_pos = m_cur_node.__position.first; + m_cur_node.type = col1.block_pos->type; + m_cur_node.position = m_elem_pos; +} + +template +side_iterator<_MtvT>::side_iterator( + std::vector&& vectors, size_type elem_pos, size_type elem_size, size_type index_offset, + uintptr_t identity, end_state_type) + : m_vectors(std::move(vectors)), m_elem_pos(elem_pos), m_elem_pos_end(elem_pos + elem_size), + m_index_offset(index_offset), m_identity(identity) +{ + m_elem_pos = m_elem_pos_end; + m_cur_node.index = index_offset; + + // We can leave the position and type uninitialized since this is an end + // position which doesn't reference an actual element. +} + +template +side_iterator<_MtvT>& side_iterator<_MtvT>::operator++() +{ + ++m_cur_node.index; + size_type pos = m_cur_node.index - m_index_offset; + if (pos >= m_vectors.size()) + { + // Move to the next element position. + m_cur_node.index = m_index_offset; + ++m_elem_pos; + if (m_elem_pos >= m_elem_pos_end) + // End position has been reached. Don't update the current node. + return *this; + } + + pos = m_cur_node.index - m_index_offset; + // Get the current vector. + assert(pos < m_vectors.size()); + mtv_item& col = m_vectors[pos]; + + // Update the current node. + m_cur_node.__position = col.vector->position(col.block_pos, m_elem_pos); + m_cur_node.position = m_elem_pos; + col.block_pos = m_cur_node.__position.first; + m_cur_node.type = col.block_pos->type; + + return *this; +} + +template +side_iterator<_MtvT> side_iterator<_MtvT>::operator++(int) +{ + side_iterator tmp(*this); + operator++(); + return tmp; +} + +template +bool side_iterator<_MtvT>::operator==(const side_iterator& other) const +{ + if (m_identity != other.m_identity) + return false; + + if (m_elem_pos_end != other.m_elem_pos_end) + return false; + + if (m_elem_pos != other.m_elem_pos || m_elem_pos_end != other.m_elem_pos_end) + return false; + + return m_cur_node.index == other.m_cur_node.index; +} + +template +bool side_iterator<_MtvT>::operator!=(const side_iterator& other) const +{ + return !operator==(other); +} + +} // namespace detail + +template +collection<_MtvT>::collection() : m_mtv_size(0), m_identity(0) +{} + +template +template +collection<_MtvT>::collection(const _T& begin, const _T& end) : m_mtv_size(0), m_identity(0) +{ + size_type n = std::distance(begin, end); + m_vectors.reserve(n); + + for (_T it = begin; it != end; ++it) + init_insert_vector(*it); + + // Create a single value that identifies the whole collection. + auto it = m_vectors.begin(); + uintptr_t identity = reinterpret_cast(*it); + ++it; + std::for_each(it, m_vectors.end(), [&](const mtv_type* p0) { + uintptr_t p = reinterpret_cast(p0); + identity = identity << 1; + identity ^= p; + }); + m_identity = identity; + + assert(m_mtv_size); // This should have been set by this point. + m_elem_range.start = 0; + m_elem_range.size = m_mtv_size; + + m_col_range.start = 0; + m_col_range.size = n; +} + +template +typename collection<_MtvT>::const_iterator collection<_MtvT>::begin() const +{ + return const_iterator( + build_iterator_state(), m_elem_range.start, m_elem_range.size, m_col_range.start, m_identity, + const_iterator::begin_state); +} + +template +typename collection<_MtvT>::const_iterator collection<_MtvT>::end() const +{ + return const_iterator( + build_iterator_state(), m_elem_range.start, m_elem_range.size, m_col_range.start, m_identity, + const_iterator::end_state); +} + +template +typename collection<_MtvT>::size_type collection<_MtvT>::size() const +{ + return m_mtv_size; +} + +template +void collection<_MtvT>::swap(collection& other) +{ + m_vectors.swap(other.m_vectors); + std::swap(m_mtv_size, other.m_mtv_size); + std::swap(m_identity, other.m_identity); + std::swap(m_elem_range, other.m_elem_range); + std::swap(m_col_range, other.m_col_range); +} + +template +void collection<_MtvT>::set_collection_range(size_type start, size_type size) +{ + check_collection_range(start, size); + m_col_range.start = start; + m_col_range.size = size; +} + +template +void collection<_MtvT>::set_element_range(size_type start, size_type size) +{ + check_element_range(start, size); + m_elem_range.start = start; + m_elem_range.size = size; +} + +template +void collection<_MtvT>::check_collection_range(size_type start, size_type size) const +{ + if (start >= m_vectors.size()) + { + std::ostringstream os; + os << "range start position must be less than " << m_vectors.size(); + throw invalid_arg_error(os.str()); + } + + if (!size) + throw invalid_arg_error("size of 0 is not allowed."); + + if ((start + size) > m_vectors.size()) + throw invalid_arg_error("size is too large."); +} + +template +void collection<_MtvT>::check_element_range(size_type start, size_type size) const +{ + if (start >= m_mtv_size) + { + std::ostringstream os; + os << "range start position must be less than " << m_mtv_size; + throw invalid_arg_error(os.str()); + } + + if (!size) + throw invalid_arg_error("size of 0 is not allowed."); + + if ((start + size) > m_mtv_size) + throw invalid_arg_error("size is too large."); +} + +template +std::vector::const_iterator::mtv_item> collection<_MtvT>::build_iterator_state() const +{ + std::vector cols; + cols.reserve(m_col_range.size); + + auto it = m_vectors.begin(); + std::advance(it, m_col_range.start); + auto it_end = it; + std::advance(it_end, m_col_range.size); + + std::for_each(it, it_end, [&](const mtv_type* p) { cols.emplace_back(p, p->cbegin(), p->cend()); }); + + return cols; +} + +template +template +void collection<_MtvT>::init_insert_vector(const _T& t, typename std::enable_if::value>::type*) +{ + check_vector_size(*t); + m_vectors.emplace_back(t); +} + +template +void collection<_MtvT>::init_insert_vector(const std::unique_ptr& p) +{ + check_vector_size(*p); + m_vectors.emplace_back(p.get()); +} + +template +void collection<_MtvT>::init_insert_vector(const std::shared_ptr& p) +{ + check_vector_size(*p); + m_vectors.emplace_back(p.get()); +} + +template +template +void collection<_MtvT>::init_insert_vector(const _T& t, typename std::enable_if::value>::type*) +{ + check_vector_size(t); + m_vectors.emplace_back(&t); +} + +template +void collection<_MtvT>::check_vector_size(const mtv_type& t) +{ + if (t.empty()) + throw invalid_arg_error("Empty multi_type_vector instance is not allowed."); + + if (!m_mtv_size) + m_mtv_size = t.size(); + else if (m_mtv_size != t.size()) + throw invalid_arg_error("All multi_type_vector instances must be of the same length."); +} + +}} // namespace mdds::mtv diff --git a/include/mdds/multi_type_vector/env.hpp b/include/mdds/multi_type_vector/env.hpp new file mode 100644 index 0000000..ee81179 --- /dev/null +++ b/include/mdds/multi_type_vector/env.hpp @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2022 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#pragma once + +#ifndef MDDS_MTV_USE_STANDARD_ELEMENT_BLOCKS +#define MDDS_MTV_USE_STANDARD_ELEMENT_BLOCKS 1 +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/iterator_node.hpp b/include/mdds/multi_type_vector/iterator_node.hpp new file mode 100644 index 0000000..bc8b58e --- /dev/null +++ b/include/mdds/multi_type_vector/iterator_node.hpp @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_ITERATOR_NODE_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_ITERATOR_NODE_HPP + +namespace mdds { namespace detail { namespace mtv { + +/** + * Node that represents the content of each iterator. The private data part + * is an implementation detail that should never be accessed externally. + * What the end position stores in its private data is totally & + * intentionally undefined. + */ +template +struct iterator_value_node +{ + using parent_type = ParentT; + using size_type = SizeT; + + mdds::mtv::element_t type; + size_type position; + size_type size; + mdds::mtv::base_element_block* data; + + iterator_value_node(const parent_type* parent, size_type block_index) + : type(mdds::mtv::element_type_empty), position(0), size(0), data(nullptr), __private_data(parent, block_index) + {} + + void swap(iterator_value_node& other) + { + std::swap(type, other.type); + std::swap(position, other.position); + std::swap(size, other.size); + std::swap(data, other.data); + + __private_data.swap(other.__private_data); + } + + struct private_data + { + const parent_type* parent; + size_type block_index; + + private_data() : parent(nullptr), block_index(0) + {} + private_data(const parent_type* _parent, size_type _block_index) : parent(_parent), block_index(_block_index) + {} + + void swap(private_data& other) + { + std::swap(parent, other.parent); + std::swap(block_index, other.block_index); + } + }; + private_data __private_data; + + bool operator==(const iterator_value_node& other) const + { + return type == other.type && position == other.position && size == other.size && data == other.data && + __private_data.parent == other.__private_data.parent && + __private_data.block_index == other.__private_data.block_index; + } + + bool operator!=(const iterator_value_node& other) const + { + return !operator==(other); + } +}; + +template +struct private_data_no_update +{ + using node_type = iterator_value_node; + + static void inc(node_type&) + {} + static void dec(node_type&) + {} +}; + +template +struct private_data_forward_update +{ + using node_type = iterator_value_node; + + static void inc(node_type& nd) + { + ++nd.__private_data.block_index; + } + + static void dec(node_type& nd) + { + --nd.__private_data.block_index; + } +}; + +}}} // namespace mdds::detail::mtv + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/macro.hpp b/include/mdds/multi_type_vector/macro.hpp new file mode 100644 index 0000000..aa9af3b --- /dev/null +++ b/include/mdds/multi_type_vector/macro.hpp @@ -0,0 +1,207 @@ +/************************************************************************* + * + * Copyright (c) 2012-2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_MACRO_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_MACRO_HPP + +/** + * @def MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(type, type_id, empty_value, + * block_type) + * + * @param type element value type. + * @param type_id constant value used as an ID for the value type. It should + * be of type mdds::mtv::element_t. + * @param empty_value value that should be used as the default "false" value + * for the value type. + * @param block_type block type that stores the specified value type. + * + * Defines required callback functions for multi_type_vector. + */ +#define MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(type, type_id, empty_value, block_type) \ +\ + inline mdds::mtv::element_t mdds_mtv_get_element_type(const type&) \ + { \ + return type_id; \ + } \ +\ + inline void mdds_mtv_get_empty_value(type& val) \ + { \ + val = empty_value; \ + } \ +\ + inline void mdds_mtv_set_value(mdds::mtv::base_element_block& block, size_t pos, const type& val) \ + { \ + block_type::set_value(block, pos, val); \ + } \ +\ + inline void mdds_mtv_get_value(const mdds::mtv::base_element_block& block, size_t pos, type& val) \ + { \ + block_type::get_value(block, pos, val); \ + } \ +\ + template \ + void mdds_mtv_set_values( \ + mdds::mtv::base_element_block& block, size_t pos, const type&, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::set_values(block, pos, it_begin, it_end); \ + } \ +\ + inline void mdds_mtv_append_value(mdds::mtv::base_element_block& block, const type& val) \ + { \ + block_type::append_value(block, val); \ + } \ +\ + inline void mdds_mtv_prepend_value(mdds::mtv::base_element_block& block, const type& val) \ + { \ + block_type::prepend_value(block, val); \ + } \ +\ + template \ + void mdds_mtv_prepend_values( \ + mdds::mtv::base_element_block& block, const type&, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::prepend_values(block, it_begin, it_end); \ + } \ +\ + template \ + void mdds_mtv_append_values( \ + mdds::mtv::base_element_block& block, const type&, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::append_values(block, it_begin, it_end); \ + } \ +\ + template \ + void mdds_mtv_assign_values( \ + mdds::mtv::base_element_block& dest, const type&, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::assign_values(dest, it_begin, it_end); \ + } \ +\ + template \ + void mdds_mtv_insert_values( \ + mdds::mtv::base_element_block& block, size_t pos, const type&, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::insert_values(block, pos, it_begin, it_end); \ + } \ +\ + inline mdds::mtv::base_element_block* mdds_mtv_create_new_block(size_t init_size, const type& val) \ + { \ + return block_type::create_block_with_value(init_size, val); \ + } \ +\ + template \ + mdds::mtv::base_element_block* mdds_mtv_create_new_block(const type&, const _Iter& it_begin, const _Iter& it_end) \ + { \ + return block_type::create_block_with_values(it_begin, it_end); \ + } + +/** + * @def MDDS_MTV_DEFINE_ELEMENT_CALLBACKS_PTR(type, type_id, empty_value, + * block_type) + * + * A variant of MDDS_MTV_DEFINE_ELEMENT_CALLBACKS that should be used for a + * pointer type. + */ +#define MDDS_MTV_DEFINE_ELEMENT_CALLBACKS_PTR(type, type_id, empty_value, block_type) \ +\ + inline mdds::mtv::element_t mdds_mtv_get_element_type(const type*) \ + { \ + return type_id; \ + } \ +\ + inline void mdds_mtv_get_empty_value(type*& val) \ + { \ + val = empty_value; \ + } \ +\ + inline void mdds_mtv_set_value(mdds::mtv::base_element_block& block, size_t pos, type* val) \ + { \ + block_type::set_value(block, pos, val); \ + } \ +\ + inline void mdds_mtv_get_value(const mdds::mtv::base_element_block& block, size_t pos, type*& val) \ + { \ + block_type::get_value(block, pos, val); \ + } \ +\ + template \ + void mdds_mtv_set_values( \ + mdds::mtv::base_element_block& block, size_t pos, const type*, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::set_values(block, pos, it_begin, it_end); \ + } \ +\ + inline void mdds_mtv_append_value(mdds::mtv::base_element_block& block, type* val) \ + { \ + block_type::append_value(block, val); \ + } \ +\ + inline void mdds_mtv_prepend_value(mdds::mtv::base_element_block& block, type* val) \ + { \ + block_type::prepend_value(block, val); \ + } \ +\ + template \ + void mdds_mtv_prepend_values( \ + mdds::mtv::base_element_block& block, const type*, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::prepend_values(block, it_begin, it_end); \ + } \ +\ + template \ + void mdds_mtv_append_values( \ + mdds::mtv::base_element_block& block, const type*, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::append_values(block, it_begin, it_end); \ + } \ +\ + template \ + void mdds_mtv_assign_values( \ + mdds::mtv::base_element_block& dest, const type*, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::assign_values(dest, it_begin, it_end); \ + } \ +\ + template \ + void mdds_mtv_insert_values( \ + mdds::mtv::base_element_block& block, size_t pos, const type*, const _Iter& it_begin, const _Iter& it_end) \ + { \ + block_type::insert_values(block, pos, it_begin, it_end); \ + } \ +\ + inline mdds::mtv::base_element_block* mdds_mtv_create_new_block(size_t init_size, type* val) \ + { \ + return block_type::create_block_with_value(init_size, val); \ + } \ +\ + template \ + mdds::mtv::base_element_block* mdds_mtv_create_new_block(const type*, const _Iter& it_begin, const _Iter& it_end) \ + { \ + return block_type::create_block_with_values(it_begin, it_end); \ + } + +#endif diff --git a/include/mdds/multi_type_vector/soa/Makefile.am b/include/mdds/multi_type_vector/soa/Makefile.am new file mode 100644 index 0000000..2730952 --- /dev/null +++ b/include/mdds/multi_type_vector/soa/Makefile.am @@ -0,0 +1,8 @@ + +headersdir = $(includedir)/mdds-@API_VERSION@/mdds/multi_type_vector/soa + +headers_HEADERS = \ + block_util.hpp \ + iterator.hpp \ + main_def.inl \ + main.hpp diff --git a/include/mdds/multi_type_vector/soa/Makefile.in b/include/mdds/multi_type_vector/soa/Makefile.in new file mode 100644 index 0000000..06616f6 --- /dev/null +++ b/include/mdds/multi_type_vector/soa/Makefile.in @@ -0,0 +1,585 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = include/mdds/multi_type_vector/soa +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_17.m4 \ + $(top_srcdir)/m4/m4_ax_valgrind_check.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(headers_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(headersdir)" +HEADERS = $(headers_HEADERS) +am__extra_recursive_targets = check-valgrind-recursive \ + check-valgrind-memcheck-recursive \ + check-valgrind-helgrind-recursive check-valgrind-drd-recursive \ + check-valgrind-sgcheck-recursive +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +API_VERSION = @API_VERSION@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_NODEBUG = @CPPFLAGS_NODEBUG@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CXX = @CXX@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CXXFLAGS_UNITTESTS = @CXXFLAGS_UNITTESTS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DOXYGEN = @DOXYGEN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +ENABLE_VALGRIND_drd = @ENABLE_VALGRIND_drd@ +ENABLE_VALGRIND_helgrind = @ENABLE_VALGRIND_helgrind@ +ENABLE_VALGRIND_memcheck = @ENABLE_VALGRIND_memcheck@ +ENABLE_VALGRIND_sgcheck = @ENABLE_VALGRIND_sgcheck@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +EXPECT = @EXPECT@ +GDB = @GDB@ +HAVE_CXX17 = @HAVE_CXX17@ +INCDIR = @INCDIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MISCDIR = @MISCDIR@ +MKDIR_P = @MKDIR_P@ +OBJDIR = @OBJDIR@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +QUICKCHECKDIR = @QUICKCHECKDIR@ +RUNTEST_BIN = @RUNTEST_BIN@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINX = @SPHINX@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VALGRIND_ENABLED = @VALGRIND_ENABLED@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +valgrind_enabled_tools = @valgrind_enabled_tools@ +valgrind_tools = @valgrind_tools@ +headersdir = $(includedir)/mdds-@API_VERSION@/mdds/multi_type_vector/soa +headers_HEADERS = \ + block_util.hpp \ + iterator.hpp \ + main_def.inl \ + main.hpp + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/mdds/multi_type_vector/soa/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign include/mdds/multi_type_vector/soa/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-headersHEADERS: $(headers_HEADERS) + @$(NORMAL_INSTALL) + @list='$(headers_HEADERS)'; test -n "$(headersdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(headersdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(headersdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(headersdir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(headersdir)" || exit $$?; \ + done + +uninstall-headersHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(headers_HEADERS)'; test -n "$(headersdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(headersdir)'; $(am__uninstall_files_from_dir) +check-valgrind-local: +check-valgrind-memcheck-local: +check-valgrind-helgrind-local: +check-valgrind-drd-local: +check-valgrind-sgcheck-local: + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(headersdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +check-valgrind: check-valgrind-am + +check-valgrind-am: check-valgrind-local + +check-valgrind-drd: check-valgrind-drd-am + +check-valgrind-drd-am: check-valgrind-drd-local + +check-valgrind-helgrind: check-valgrind-helgrind-am + +check-valgrind-helgrind-am: check-valgrind-helgrind-local + +check-valgrind-memcheck: check-valgrind-memcheck-am + +check-valgrind-memcheck-am: check-valgrind-memcheck-local + +check-valgrind-sgcheck: check-valgrind-sgcheck-am + +check-valgrind-sgcheck-am: check-valgrind-sgcheck-local + +clean: clean-am + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-headersHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-headersHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am check-valgrind-am \ + check-valgrind-drd-am check-valgrind-drd-local \ + check-valgrind-helgrind-am check-valgrind-helgrind-local \ + check-valgrind-local check-valgrind-memcheck-am \ + check-valgrind-memcheck-local check-valgrind-sgcheck-am \ + check-valgrind-sgcheck-local clean clean-generic cscopelist-am \ + ctags ctags-am distclean distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am \ + install-headersHEADERS install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic pdf \ + pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-headersHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/include/mdds/multi_type_vector/soa/block_util.hpp b/include/mdds/multi_type_vector/soa/block_util.hpp new file mode 100644 index 0000000..62f1926 --- /dev/null +++ b/include/mdds/multi_type_vector/soa/block_util.hpp @@ -0,0 +1,621 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_SOA_BLOCK_UTIL_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_SOA_BLOCK_UTIL_HPP + +#include "mdds/global.hpp" +#include "../types.hpp" + +#if defined(__SSE2__) +#include +#endif +#if defined(__AVX2__) +#include +#endif + +namespace mdds { namespace mtv { namespace soa { namespace detail { + +template +struct adjust_block_positions +{ + void operator()(Blks& /*block_store*/, int64_t /*start_block_index*/, int64_t /*delta*/) const + { + static_assert( + mdds::detail::invalid_static_int, "The loop-unrolling factor must be one of 0, 4, 8, 16, or 32."); + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < n; ++i) + block_store.positions[i] += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 4. + int64_t len = n - start_block_index; + int64_t rem = len & 3; // % 4 + len -= rem; + len += start_block_index; +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 4) + { + block_store.positions[i + 0] += delta; + block_store.positions[i + 1] += delta; + block_store.positions[i + 2] += delta; + block_store.positions[i + 3] += delta; + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 8. + int64_t len = n - start_block_index; + int64_t rem = len & 7; // % 8 + len -= rem; + len += start_block_index; +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 8) + { + block_store.positions[i + 0] += delta; + block_store.positions[i + 1] += delta; + block_store.positions[i + 2] += delta; + block_store.positions[i + 3] += delta; + block_store.positions[i + 4] += delta; + block_store.positions[i + 5] += delta; + block_store.positions[i + 6] += delta; + block_store.positions[i + 7] += delta; + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 16. + int64_t len = n - start_block_index; + int64_t rem = len & 15; // % 16 + len -= rem; + len += start_block_index; +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 16) + { + block_store.positions[i + 0] += delta; + block_store.positions[i + 1] += delta; + block_store.positions[i + 2] += delta; + block_store.positions[i + 3] += delta; + block_store.positions[i + 4] += delta; + block_store.positions[i + 5] += delta; + block_store.positions[i + 6] += delta; + block_store.positions[i + 7] += delta; + block_store.positions[i + 8] += delta; + block_store.positions[i + 9] += delta; + block_store.positions[i + 10] += delta; + block_store.positions[i + 11] += delta; + block_store.positions[i + 12] += delta; + block_store.positions[i + 13] += delta; + block_store.positions[i + 14] += delta; + block_store.positions[i + 15] += delta; + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 32. + int64_t len = n - start_block_index; + int64_t rem = len & 31; // % 32 + len -= rem; + len += start_block_index; +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 32) + { + block_store.positions[i + 0] += delta; + block_store.positions[i + 1] += delta; + block_store.positions[i + 2] += delta; + block_store.positions[i + 3] += delta; + block_store.positions[i + 4] += delta; + block_store.positions[i + 5] += delta; + block_store.positions[i + 6] += delta; + block_store.positions[i + 7] += delta; + block_store.positions[i + 8] += delta; + block_store.positions[i + 9] += delta; + block_store.positions[i + 10] += delta; + block_store.positions[i + 11] += delta; + block_store.positions[i + 12] += delta; + block_store.positions[i + 13] += delta; + block_store.positions[i + 14] += delta; + block_store.positions[i + 15] += delta; + block_store.positions[i + 16] += delta; + block_store.positions[i + 17] += delta; + block_store.positions[i + 18] += delta; + block_store.positions[i + 19] += delta; + block_store.positions[i + 20] += delta; + block_store.positions[i + 21] += delta; + block_store.positions[i + 22] += delta; + block_store.positions[i + 23] += delta; + block_store.positions[i + 24] += delta; + block_store.positions[i + 25] += delta; + block_store.positions[i + 26] += delta; + block_store.positions[i + 27] += delta; + block_store.positions[i + 28] += delta; + block_store.positions[i + 29] += delta; + block_store.positions[i + 30] += delta; + block_store.positions[i + 31] += delta; + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +#if defined(__SSE2__) + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + static_assert( + sizeof(typename decltype(block_store.positions)::value_type) == 8, + "This code works only when the position values are 64-bit wide."); + + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 2. + int64_t len = n - start_block_index; + bool odd = len & 1; + if (odd) + len -= 1; + + len += start_block_index; + + __m128i right = _mm_set_epi64x(delta, delta); + +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 2) + { + __m128i* dst = (__m128i*)&block_store.positions[i]; + _mm_storeu_si128(dst, _mm_add_epi64(_mm_loadu_si128(dst), right)); + } + + if (odd) + block_store.positions[len] += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + static_assert( + sizeof(typename decltype(block_store.positions)::value_type) == 8, + "This code works only when the position values are 64-bit wide."); + + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 8. + int64_t len = n - start_block_index; + int64_t rem = len & 7; // % 8 + len -= rem; + len += start_block_index; + + __m128i right = _mm_set_epi64x(delta, delta); + +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 8) + { + __m128i* dst0 = (__m128i*)&block_store.positions[i]; + _mm_storeu_si128(dst0, _mm_add_epi64(_mm_loadu_si128(dst0), right)); + + __m128i* dst2 = (__m128i*)&block_store.positions[i + 2]; + _mm_storeu_si128(dst2, _mm_add_epi64(_mm_loadu_si128(dst2), right)); + + __m128i* dst4 = (__m128i*)&block_store.positions[i + 4]; + _mm_storeu_si128(dst4, _mm_add_epi64(_mm_loadu_si128(dst4), right)); + + __m128i* dst6 = (__m128i*)&block_store.positions[i + 6]; + _mm_storeu_si128(dst6, _mm_add_epi64(_mm_loadu_si128(dst6), right)); + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + static_assert( + sizeof(typename decltype(block_store.positions)::value_type) == 8, + "This code works only when the position values are 64-bit wide."); + + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 16. + int64_t len = n - start_block_index; + int64_t rem = len & 15; // % 16 + len -= rem; + len += start_block_index; + + __m128i right = _mm_set_epi64x(delta, delta); + +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 16) + { + __m128i* dst0 = (__m128i*)&block_store.positions[i]; + _mm_storeu_si128(dst0, _mm_add_epi64(_mm_loadu_si128(dst0), right)); + + __m128i* dst2 = (__m128i*)&block_store.positions[i + 2]; + _mm_storeu_si128(dst2, _mm_add_epi64(_mm_loadu_si128(dst2), right)); + + __m128i* dst4 = (__m128i*)&block_store.positions[i + 4]; + _mm_storeu_si128(dst4, _mm_add_epi64(_mm_loadu_si128(dst4), right)); + + __m128i* dst6 = (__m128i*)&block_store.positions[i + 6]; + _mm_storeu_si128(dst6, _mm_add_epi64(_mm_loadu_si128(dst6), right)); + + __m128i* dst8 = (__m128i*)&block_store.positions[i + 8]; + _mm_storeu_si128(dst8, _mm_add_epi64(_mm_loadu_si128(dst8), right)); + + __m128i* dst10 = (__m128i*)&block_store.positions[i + 10]; + _mm_storeu_si128(dst10, _mm_add_epi64(_mm_loadu_si128(dst10), right)); + + __m128i* dst12 = (__m128i*)&block_store.positions[i + 12]; + _mm_storeu_si128(dst12, _mm_add_epi64(_mm_loadu_si128(dst12), right)); + + __m128i* dst14 = (__m128i*)&block_store.positions[i + 14]; + _mm_storeu_si128(dst14, _mm_add_epi64(_mm_loadu_si128(dst14), right)); + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + static_assert( + sizeof(typename decltype(block_store.positions)::value_type) == 8, + "This code works only when the position values are 64-bit wide."); + + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 32. + int64_t len = n - start_block_index; + int64_t rem = len & 31; // % 32 + len -= rem; + len += start_block_index; + + __m128i right = _mm_set_epi64x(delta, delta); + +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 32) + { + __m128i* dst0 = (__m128i*)&block_store.positions[i]; + _mm_storeu_si128(dst0, _mm_add_epi64(_mm_loadu_si128(dst0), right)); + + __m128i* dst2 = (__m128i*)&block_store.positions[i + 2]; + _mm_storeu_si128(dst2, _mm_add_epi64(_mm_loadu_si128(dst2), right)); + + __m128i* dst4 = (__m128i*)&block_store.positions[i + 4]; + _mm_storeu_si128(dst4, _mm_add_epi64(_mm_loadu_si128(dst4), right)); + + __m128i* dst6 = (__m128i*)&block_store.positions[i + 6]; + _mm_storeu_si128(dst6, _mm_add_epi64(_mm_loadu_si128(dst6), right)); + + __m128i* dst8 = (__m128i*)&block_store.positions[i + 8]; + _mm_storeu_si128(dst8, _mm_add_epi64(_mm_loadu_si128(dst8), right)); + + __m128i* dst10 = (__m128i*)&block_store.positions[i + 10]; + _mm_storeu_si128(dst10, _mm_add_epi64(_mm_loadu_si128(dst10), right)); + + __m128i* dst12 = (__m128i*)&block_store.positions[i + 12]; + _mm_storeu_si128(dst12, _mm_add_epi64(_mm_loadu_si128(dst12), right)); + + __m128i* dst14 = (__m128i*)&block_store.positions[i + 14]; + _mm_storeu_si128(dst14, _mm_add_epi64(_mm_loadu_si128(dst14), right)); + + __m128i* dst16 = (__m128i*)&block_store.positions[i + 16]; + _mm_storeu_si128(dst16, _mm_add_epi64(_mm_loadu_si128(dst16), right)); + + __m128i* dst18 = (__m128i*)&block_store.positions[i + 18]; + _mm_storeu_si128(dst18, _mm_add_epi64(_mm_loadu_si128(dst18), right)); + + __m128i* dst20 = (__m128i*)&block_store.positions[i + 20]; + _mm_storeu_si128(dst20, _mm_add_epi64(_mm_loadu_si128(dst20), right)); + + __m128i* dst22 = (__m128i*)&block_store.positions[i + 22]; + _mm_storeu_si128(dst22, _mm_add_epi64(_mm_loadu_si128(dst22), right)); + + __m128i* dst24 = (__m128i*)&block_store.positions[i + 24]; + _mm_storeu_si128(dst24, _mm_add_epi64(_mm_loadu_si128(dst24), right)); + + __m128i* dst26 = (__m128i*)&block_store.positions[i + 26]; + _mm_storeu_si128(dst26, _mm_add_epi64(_mm_loadu_si128(dst26), right)); + + __m128i* dst28 = (__m128i*)&block_store.positions[i + 28]; + _mm_storeu_si128(dst28, _mm_add_epi64(_mm_loadu_si128(dst28), right)); + + __m128i* dst30 = (__m128i*)&block_store.positions[i + 30]; + _mm_storeu_si128(dst30, _mm_add_epi64(_mm_loadu_si128(dst30), right)); + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +#endif // __SSE2__ + +#if defined(__AVX2__) + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + static_assert( + sizeof(typename decltype(block_store.positions)::value_type) == 8, + "This code works only when the position values are 64-bit wide."); + + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 4. + int64_t len = n - start_block_index; + int64_t rem = len & 3; // % 4 + len -= rem; + len += start_block_index; + + __m256i right = _mm256_set1_epi64x(delta); + +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 4) + { + __m256i* dst = (__m256i*)&block_store.positions[i]; + _mm256_storeu_si256(dst, _mm256_add_epi64(_mm256_loadu_si256(dst), right)); + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + static_assert( + sizeof(typename decltype(block_store.positions)::value_type) == 8, + "This code works only when the position values are 64-bit wide."); + + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 16. + int64_t len = n - start_block_index; + int64_t rem = len & 15; // % 16 + len -= rem; + len += start_block_index; + + __m256i right = _mm256_set1_epi64x(delta); + +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 16) + { + __m256i* dst = (__m256i*)&block_store.positions[i]; + _mm256_storeu_si256(dst, _mm256_add_epi64(_mm256_loadu_si256(dst), right)); + + __m256i* dst4 = (__m256i*)&block_store.positions[i + 4]; + _mm256_storeu_si256(dst4, _mm256_add_epi64(_mm256_loadu_si256(dst4), right)); + + __m256i* dst8 = (__m256i*)&block_store.positions[i + 8]; + _mm256_storeu_si256(dst8, _mm256_add_epi64(_mm256_loadu_si256(dst8), right)); + + __m256i* dst12 = (__m256i*)&block_store.positions[i + 12]; + _mm256_storeu_si256(dst12, _mm256_add_epi64(_mm256_loadu_si256(dst12), right)); + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +template +struct adjust_block_positions +{ + void operator()(Blks& block_store, int64_t start_block_index, int64_t delta) const + { + static_assert( + sizeof(typename decltype(block_store.positions)::value_type) == 8, + "This code works only when the position values are 64-bit wide."); + + int64_t n = block_store.positions.size(); + + if (start_block_index >= n) + return; + + // Ensure that the section length is divisible by 16. + int64_t len = n - start_block_index; + int64_t rem = len & 31; // % 32 + len -= rem; + len += start_block_index; + + __m256i right = _mm256_set1_epi64x(delta); + +#if MDDS_USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = start_block_index; i < len; i += 32) + { + __m256i* dst = (__m256i*)&block_store.positions[i]; + _mm256_storeu_si256(dst, _mm256_add_epi64(_mm256_loadu_si256(dst), right)); + + __m256i* dst4 = (__m256i*)&block_store.positions[i + 4]; + _mm256_storeu_si256(dst4, _mm256_add_epi64(_mm256_loadu_si256(dst4), right)); + + __m256i* dst8 = (__m256i*)&block_store.positions[i + 8]; + _mm256_storeu_si256(dst8, _mm256_add_epi64(_mm256_loadu_si256(dst8), right)); + + __m256i* dst12 = (__m256i*)&block_store.positions[i + 12]; + _mm256_storeu_si256(dst12, _mm256_add_epi64(_mm256_loadu_si256(dst12), right)); + + __m256i* dst16 = (__m256i*)&block_store.positions[i + 16]; + _mm256_storeu_si256(dst16, _mm256_add_epi64(_mm256_loadu_si256(dst16), right)); + + __m256i* dst20 = (__m256i*)&block_store.positions[i + 20]; + _mm256_storeu_si256(dst20, _mm256_add_epi64(_mm256_loadu_si256(dst20), right)); + + __m256i* dst24 = (__m256i*)&block_store.positions[i + 24]; + _mm256_storeu_si256(dst24, _mm256_add_epi64(_mm256_loadu_si256(dst24), right)); + + __m256i* dst28 = (__m256i*)&block_store.positions[i + 28]; + _mm256_storeu_si256(dst28, _mm256_add_epi64(_mm256_loadu_si256(dst28), right)); + } + + rem += len; + for (int64_t i = len; i < rem; ++i) + block_store.positions[i] += delta; + } +}; + +#endif // __AVX2__ + +}}}} // namespace mdds::mtv::soa::detail + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/soa/iterator.hpp b/include/mdds/multi_type_vector/soa/iterator.hpp new file mode 100644 index 0000000..c8339c9 --- /dev/null +++ b/include/mdds/multi_type_vector/soa/iterator.hpp @@ -0,0 +1,414 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_SOA_ITERATOR_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_SOA_ITERATOR_HPP + +#include "../iterator_node.hpp" + +#include + +namespace mdds { namespace mtv { namespace soa { namespace detail { + +/** + * Common base for both const and non-const iterators to handle traversal of + * the internal source iterators. + * + * Its protected inc() and dec() methods have non-const return type, and the + * derived classes wrap them and return values with their respective const + * modifiers. + * + * The trait struct needs to have the following static member types: + *
    + *
  • parent
  • + *
  • positions_type
  • + *
  • sizes_type
  • + *
  • element_blocks_type
  • + *
+ */ +template +class iterator_updater +{ +protected: + using parent_type = typename Traits::parent; + using positions_type = typename Traits::positions_type; + using sizes_type = typename Traits::sizes_type; + using element_blocks_type = typename Traits::element_blocks_type; + using size_type = typename Traits::parent::size_type; + + using node = mdds::detail::mtv::iterator_value_node; + + using positions_iterator_type = typename Traits::positions_iterator_type; + using sizes_iterator_type = typename Traits::sizes_iterator_type; + using element_blocks_iterator_type = typename Traits::element_blocks_iterator_type; + + /** + * This struct groups together the iterators for the three array types for + * easy synchronized traversal of the arrays. + */ + struct grouped_iterator_type + { + positions_iterator_type position_iterator; + sizes_iterator_type size_iterator; + element_blocks_iterator_type element_block_iterator; + + void inc() + { + ++position_iterator; + ++size_iterator; + ++element_block_iterator; + } + + void dec() + { + --position_iterator; + --size_iterator; + --element_block_iterator; + } + + bool operator==(const grouped_iterator_type& other) const + { + return position_iterator == other.position_iterator && size_iterator == other.size_iterator && + element_block_iterator == other.element_block_iterator; + } + + bool operator!=(const grouped_iterator_type& other) const + { + return !operator==(other); + } + + grouped_iterator_type() = default; + + grouped_iterator_type( + const positions_iterator_type& itr_pos, const sizes_iterator_type& itr_size, + const element_blocks_iterator_type& itr_elem_blocks) + : position_iterator(itr_pos), size_iterator(itr_size), element_block_iterator(itr_elem_blocks) + {} + }; + + node m_cur_node; + grouped_iterator_type m_pos; + grouped_iterator_type m_end; + + iterator_updater() : m_cur_node(nullptr, 0) + {} + + iterator_updater( + const grouped_iterator_type& pos, const grouped_iterator_type& end, const parent_type* parent, + size_type block_index) + : m_cur_node(parent, block_index), m_pos(pos), m_end(end) + { + if (m_pos != m_end) + update_node(); + } + + iterator_updater( + const positions_iterator_type& positions_pos, const sizes_iterator_type& sizes_pos, + const element_blocks_iterator_type& eb_pos, const positions_iterator_type& positions_end, + const sizes_iterator_type& sizes_end, const element_blocks_iterator_type& eb_end, const parent_type* parent, + size_type block_index) + : iterator_updater({positions_pos, sizes_pos, eb_pos}, {positions_end, sizes_end, eb_end}, parent, block_index) + {} + + iterator_updater(const iterator_updater& other) + : m_cur_node(other.m_cur_node), m_pos(other.m_pos), m_end(other.m_end) + {} + + void update_node() + { +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + if (m_pos == m_end) + throw general_error("Current node position should never equal the end position during node update."); +#endif + + m_cur_node.position = *m_pos.position_iterator; + m_cur_node.size = *m_pos.size_iterator; + m_cur_node.data = *m_pos.element_block_iterator; + + if (m_cur_node.data) + m_cur_node.type = mdds::mtv::get_block_type(*m_cur_node.data); + else + m_cur_node.type = mdds::mtv::element_type_empty; + } + + node* inc() + { + m_pos.inc(); + if (m_pos == m_end) + return nullptr; + + update_node(); + return &m_cur_node; + } + + node* dec() + { + m_pos.dec(); + update_node(); + return &m_cur_node; + } + + void _print_state(std::ostream& os) const + { + auto prev_flags = os.flags(); + os << "parent=" << std::hex << m_cur_node.__private_data.parent + << "; block-index=" << m_cur_node.__private_data.block_index << "; position=" << m_cur_node.position + << "; size=" << m_cur_node.size << "; type=" << m_cur_node.type << "; data=" << m_cur_node.data; + os.flags(prev_flags); + } + +public: + bool operator==(const iterator_updater& other) const + { + if (m_pos != m_end && other.m_pos != other.m_end) + { + // TODO: Set hard-coded values to the current node for the end + // position nodes to remove this if block. + if (m_cur_node != other.m_cur_node) + return false; + } + return m_pos == other.m_pos && m_end == other.m_end; + } + + bool operator!=(const iterator_updater& other) const + { + return !operator==(other); + } + + iterator_updater& operator=(const iterator_updater& other) + { + m_cur_node = other.m_cur_node; + m_pos = other.m_pos; + m_end = other.m_end; + return *this; + } + + void swap(iterator_updater& other) + { + m_cur_node.swap(other.m_cur_node); + std::swap(m_pos, other.m_pos); + std::swap(m_end, other.m_end); + } + + const node& get_node() const + { + return m_cur_node; + } + const grouped_iterator_type& get_pos() const + { + return m_pos; + } + const grouped_iterator_type& get_end() const + { + return m_end; + } +}; + +template +class iterator_base : public iterator_updater +{ + using parent_type = typename Traits::parent; + using node_update_func = typename Traits::private_data_update; + using updater = iterator_updater; + + using grouped_iterator_type = typename updater::grouped_iterator_type; + using size_type = typename updater::size_type; + + using updater::dec; + using updater::inc; + using updater::m_cur_node; + +public: + using updater::get_end; + using updater::get_pos; + + // iterator traits + using value_type = typename updater::node; + using pointer = value_type*; + using reference = value_type&; + using difference_type = ptrdiff_t; + using iterator_category = std::bidirectional_iterator_tag; + +public: + iterator_base() + {} + iterator_base( + const grouped_iterator_type& pos, const grouped_iterator_type& end, const parent_type* parent, + size_type block_index) + : updater(pos, end, parent, block_index) + {} + + value_type& operator*() + { + return m_cur_node; + } + + const value_type& operator*() const + { + return m_cur_node; + } + + value_type* operator->() + { + return &m_cur_node; + } + + const value_type* operator->() const + { + return &m_cur_node; + } + + iterator_base& operator++() + { + node_update_func::inc(m_cur_node); + inc(); + return *this; + } + + iterator_base& operator--() + { + dec(); + node_update_func::dec(m_cur_node); + return *this; + } + + void _print_state(std::ostream& os) const + { + os << "{iterator: "; + updater::_print_state(os); + os << "}"; + } +}; + +template +class const_iterator_base : public iterator_updater +{ + using parent_type = typename Traits::parent; + using node_update_func = typename Traits::private_data_update; + using updater = iterator_updater; + + using grouped_iterator_type = typename updater::grouped_iterator_type; + using size_type = typename updater::size_type; + + using updater::dec; + using updater::inc; + using updater::m_cur_node; + +public: + using updater::get_end; + using updater::get_pos; + + using iterator_base = NonConstItrBase; + + // iterator traits + using value_type = typename updater::node; + using pointer = value_type*; + using reference = value_type&; + using difference_type = ptrdiff_t; + using iterator_category = std::bidirectional_iterator_tag; + +public: + const_iterator_base() : updater() + {} + const_iterator_base( + const grouped_iterator_type& pos, const grouped_iterator_type& end, const parent_type* parent, + size_type block_index) + : updater(pos, end, parent, block_index) + {} + + /** + * Take the non-const iterator counterpart to create a const iterator. + */ + const_iterator_base(const iterator_base& other) + : updater( + other.get_pos().position_iterator, other.get_pos().size_iterator, other.get_pos().element_block_iterator, + other.get_end().position_iterator, other.get_end().size_iterator, other.get_end().element_block_iterator, + other.get_node().__private_data.parent, other.get_node().__private_data.block_index) + {} + + const value_type& operator*() const + { + return m_cur_node; + } + + const value_type* operator->() const + { + return &m_cur_node; + } + + const_iterator_base& operator++() + { + node_update_func::inc(m_cur_node); + inc(); + return *this; + } + + const_iterator_base& operator--() + { + dec(); + node_update_func::dec(m_cur_node); + return *this; + } + + bool operator==(const const_iterator_base& other) const + { + return updater::operator==(other); + } + + bool operator!=(const const_iterator_base& other) const + { + return updater::operator!=(other); + } + + void _print_state(std::ostream& os) const + { + os << "(const-iterator: "; + updater::_print_state(os); + os << ")"; + } +}; + +template +std::ostream& operator<<(std::ostream& os, const iterator_base& it) +{ + it._print_state(os); + return os; +} + +template +std::ostream& operator<<(std::ostream& os, const const_iterator_base& it) +{ + it._print_state(os); + return os; +} + +}}}} // namespace mdds::mtv::soa::detail + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/soa/main.hpp b/include/mdds/multi_type_vector/soa/main.hpp new file mode 100644 index 0000000..0519b35 --- /dev/null +++ b/include/mdds/multi_type_vector/soa/main.hpp @@ -0,0 +1,1449 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_SOA_MAIN_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_SOA_MAIN_HPP + +#include "../../global.hpp" +#include "../types.hpp" +#include "../util.hpp" +#include "./block_util.hpp" +#include "./iterator.hpp" + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG +#include +#endif + +namespace mdds { namespace mtv { namespace soa { + +/** + * Multi-type vector consists of a series of one or more blocks, and each + * block may either be empty, or stores a series of non-empty elements of + * identical type. These blocks collectively represent a single logical + * one-dimensional array that may store elements of different types. It is + * guaranteed that the block types of neighboring blocks are always + * different. + * + * Structurally, the primary array stores block instances whose types + * are of value_type, which in turn consists of the following + * data members: + * + *
    + *
  • type which indicates the block type,
  • + *
  • position which stores the logical position of the + * first element of the block,
  • + *
  • size which stores the logical size of the block, + * and
  • + *
  • data which stores the pointer to a secondary array + * (a.k.a. element block) which stores the actual element values, or + * nullptr in case the block represents an empty segment.
  • + *
+ * + * This variant implements a structure-of-arrays (SoA) storage. + * + * @see mdds::mtv::soa::multi_type_vector::value_type + */ +template +class multi_type_vector +{ +public: + using size_type = std::size_t; + + using element_block_type = mdds::mtv::base_element_block; + using element_category_type = mdds::mtv::element_t; + using block_funcs = typename Traits::block_funcs; + + /** + * Optional event handler function structure, whose functions get called + * at specific events. The following events are currently supported: + * + *
    + *
  • element_block_acquired - this gets called whenever + * the container acquires a new element block either as a result of a new + * element block creation or a tranfer of an existing + * element block from another container.
  • + *
  • element_block_released - this gets called whenever + * the container releases an existing element block either because + * the block gets deleted or gets transferred to another container.
  • + *
+ * + * @see mdds::mtv::empty_event_func for the precise function signatures of + * the event handler functions. + */ + using event_func = typename Traits::event_func; + +private: + struct block_slot_type + { + size_type position = 0; + size_type size = 0; + element_block_type* element_block = nullptr; + + block_slot_type() + {} + block_slot_type(size_type _position, size_type _size) : position(_position), size(_size) + {} + }; + + struct blocks_type + { + std::vector positions; + std::vector sizes; + std::vector element_blocks; + + blocks_type(); + blocks_type(const blocks_type& other); + blocks_type(blocks_type&& other); + + void pop_back() + { + positions.pop_back(); + sizes.pop_back(); + element_blocks.pop_back(); + } + + void push_back(size_type pos, size_type size, element_block_type* data) + { + positions.push_back(pos); + sizes.push_back(size); + element_blocks.push_back(data); + } + + void push_back(const block_slot_type& slot) + { + positions.push_back(slot.position); + sizes.push_back(slot.size); + element_blocks.push_back(slot.element_block); + } + + void erase(size_type index); + void erase(size_type index, size_type size); + void insert(size_type index, size_type size); + void insert(size_type index, size_type pos, size_type size, element_block_type* data); + void insert(size_type index, const blocks_type& new_blocks); + + /** + * Calculate the position of specified block based on the position and + * size of the previous block. + * + * @param index index of the block to calculate the position for. + */ + void calc_block_position(size_type index); + + size_type calc_next_block_position(size_type index); + + void swap(size_type index1, size_type index2); + + void swap(blocks_type& other); + + void reserve(size_type n); + + bool equals(const blocks_type& other) const; + + void clear(); + + void check_integrity() const; + }; + + struct blocks_to_transfer + { + blocks_type blocks; + size_type insert_index = 0; + }; + + struct iterator_trait + { + using parent = multi_type_vector; + using positions_type = std::vector; + using sizes_type = std::vector; + using element_blocks_type = std::vector; + + using positions_iterator_type = typename positions_type::iterator; + using sizes_iterator_type = typename sizes_type::iterator; + using element_blocks_iterator_type = typename element_blocks_type::iterator; + + using private_data_update = mdds::detail::mtv::private_data_forward_update; + }; + + struct const_iterator_trait + { + using parent = multi_type_vector; + using positions_type = std::vector; + using sizes_type = std::vector; + using element_blocks_type = std::vector; + + using positions_iterator_type = typename positions_type::const_iterator; + using sizes_iterator_type = typename sizes_type::const_iterator; + using element_blocks_iterator_type = typename element_blocks_type::const_iterator; + + using private_data_update = mdds::detail::mtv::private_data_forward_update; + }; + + struct reverse_iterator_trait + { + using parent = multi_type_vector; + using positions_type = std::vector; + using sizes_type = std::vector; + using element_blocks_type = std::vector; + + using positions_iterator_type = typename positions_type::reverse_iterator; + using sizes_iterator_type = typename sizes_type::reverse_iterator; + using element_blocks_iterator_type = typename element_blocks_type::reverse_iterator; + + using private_data_update = mdds::detail::mtv::private_data_no_update; + }; + + struct const_reverse_iterator_trait + { + using parent = multi_type_vector; + using positions_type = std::vector; + using sizes_type = std::vector; + using element_blocks_type = std::vector; + + using positions_iterator_type = typename positions_type::const_reverse_iterator; + using sizes_iterator_type = typename sizes_type::const_reverse_iterator; + using element_blocks_iterator_type = typename element_blocks_type::const_reverse_iterator; + + using private_data_update = mdds::detail::mtv::private_data_no_update; + }; + + struct element_block_deleter + { + void operator()(const element_block_type* p) + { + block_funcs::delete_block(p); + } + }; + +public: + using iterator = detail::iterator_base; + using const_iterator = detail::const_iterator_base; + + using reverse_iterator = detail::iterator_base; + using const_reverse_iterator = detail::const_iterator_base; + + using position_type = std::pair; + using const_position_type = std::pair; + + /** + * value_type is the type of a block stored in the primary array. It + * consists of the following data members: + * + *
    + *
  • type which indicates the block type,
  • + *
  • position which stores the logical position of the + * first element of the block,
  • + *
  • size which stores the logical size of the block, + * and
  • + *
  • data which stores the pointer to a secondary array + * (a.k.a. element block) which stores the actual element values, or + * nullptr in case the block represents an empty segment.
  • + *
+ */ + using value_type = mdds::detail::mtv::iterator_value_node; + + /** + * Move the position object to the next logical position. Caller must + * ensure the the position object is valid. + * + * @param pos position object. + * + * @return position object that points to the next logical position. + */ + static position_type next_position(const position_type& pos); + + /** + * Increment or decrement the position object by specified steps. Caller + * must ensure the the position object is valid. + * + * @param pos position object. + * @param steps steps to advance the position object. + * + * @return position object that points to the new logical position. + */ + static position_type advance_position(const position_type& pos, int steps); + + /** + * Move the position object to the next logical position. Caller must + * ensure the the position object is valid. + * + * @param pos position object. + * + * @return position object that points to the next logical position. + */ + static const_position_type next_position(const const_position_type& pos); + + /** + * Increment or decrement the position object by specified steps. Caller + * must ensure the the position object is valid. + * + * @param pos position object. + * @param steps steps to advance the position object. + * + * @return position object that points to the new logical position. + */ + static const_position_type advance_position(const const_position_type& pos, int steps); + + /** + * Extract the logical position from a position object. + * + * @param pos position object. + * + * @return logical position of the element that the position object + * references. + */ + static size_type logical_position(const const_position_type& pos); + + /** + * Get element value from a position object. The caller must specify the + * type of block in which the element is expected to be stored. + * + * @param pos position object. + * + * @return element value. + */ + template + static typename _Blk::value_type get(const const_position_type& pos); + + event_func& event_handler(); + const event_func& event_handler() const; + + /** + * Default constructor. It initializes the container with empty size. + */ + multi_type_vector(); + + /** + * Constructor that takes an lvalue reference to an event handler object. + * The event handler instance will be copy-constructed. + * + * @param hdl lvalue reference to an event handler object. + */ + multi_type_vector(const event_func& hdl); + + /** + * Constructor that takes an rvalue reference to an event handler object. + * The event handler instance will be move-constructed. + * + * @param hdl rvalue reference to an event handler object. + */ + multi_type_vector(event_func&& hdl); + + /** + * Constructor that takes initial size of the container. When the size + * specified is greater than 0, it initializes the container with empty + * elements. + * + * @param init_size initial container size. + */ + multi_type_vector(size_type init_size); + + /** + * Constructor that takes initial size of the container and an element + * value to initialize the elements to. When the size specified is greater + * than 0, it initializes the container with elements that are copies of + * the value specified. + * + * @param init_size initial container size. + * @param value initial element value. + */ + template + multi_type_vector(size_type init_size, const T& value); + + /** + * Constructor that takes initial size of the container and begin and end + * iterator positions that specify a series of elements to initialize the + * container to. The container will contain copies of the elements + * specified after this call returns. + * + * @param init_size initial container size. + * @param it_begin iterator that points to the begin position of the + * values the container is being initialized to. + * @param it_end iterator that points to the end position of the values + * the container is being initialized to. The end position + * is not inclusive. + */ + template + multi_type_vector(size_type init_size, const T& it_begin, const T& it_end); + + /** + * Copy constructor. + * + * @param other the other instance to copy values from. + */ + multi_type_vector(const multi_type_vector& other); + + /** + * Move constructor. + * + * @param other the other instance to move values from. + */ + multi_type_vector(multi_type_vector&& other); + + /** + * Destructor. It deletes all allocated element blocks. + */ + ~multi_type_vector(); + + /** + * Given the logical position of an element, get the iterator of the block + * where the element is located, and its offset from the first element of + * that block. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range, except + * when the specified position is the position immediately after the last + * valid position, it will return a valid position object representing + * the end position.

+ * + * @param pos logical position of the element. + * @return position object that stores an iterator referencing the element + * block where the element resides, and its offset within that + * block. + */ + position_type position(size_type pos); + + /** + * Given the logical position of an element, get the iterator of the block + * where the element is located, and its offset from the first element of + * that block. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range, except + * when the specified position is the position immediately after the last + * valid position, it will return a valid position object representing + * the end position.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the element + * position. + * @param pos logical position of the element. + * @return position object that stores an iterator referencing the element + * block where the element resides, and its offset within that + * block. + */ + position_type position(const iterator& pos_hint, size_type pos); + + /** + * Given the logical position of an element, get an iterator referencing + * the element block where the element is located, and its offset from the + * first element of that block. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element. + * @return position object that stores an iterator referencing the element + * block where the element resides, and its offset within that + * block. + */ + const_position_type position(size_type pos) const; + + /** + * Given the logical position of an element, get an iterator referencing + * the element block where the element is located, and its offset from the + * first element of that block. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the element + * position. + * @param pos logical position of the element. + * @return position object that stores an iterator referencing the element + * block where the element resides, and its offset within the + * block. + */ + const_position_type position(const const_iterator& pos_hint, size_type pos) const; + + /** + * Move elements from one container to another. After the move, the + * segment where the elements were in the source container becomes empty. + * When transferring managed elements, this call transfers ownership of + * the moved elements to the destination container. The moved elements + * will overwrite any existing elements in the destination range of the + * receiving container. Transfer of elements within the same container is + * not allowed. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is greater than or equal to + * the source container size, or the destination container is not + * large enough to accommodate the transferred elements.

+ * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @param dest destination container to which the elements are to be + * moved. + * @param dest_pos position in the destination container to which the + * elements are to be moved. + * + * @return iterator referencing the block where the moved elements were + * prior to the transfer. + */ + iterator transfer(size_type start_pos, size_type end_pos, multi_type_vector& dest, size_type dest_pos); + + /** + * Move elements from one container to another. After the move, the + * segment where the elements were in the source container becomes empty. + * When transferring managed elements, this call transfers ownership of + * the moved elements to the new container. The moved elements will + * overwrite any existing elements in the destination range of the + * receiving container. Transfer of elements within the same container is + * not allowed. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is greater than or equal to + * the source container size, or the destination container is not large + * enough to accommodate the transferred elements.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the blocks + * where the elements to be transferred reside. + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @param dest destination container to which the elements are to be + * moved. + * @param dest_pos position in the destination container to which the + * elements are to be moved. + * + * @return iterator referencing the block where the moved elements were + * prior to the transfer. + */ + iterator transfer( + const iterator& pos_hint, size_type start_pos, size_type end_pos, multi_type_vector& dest, size_type dest_pos); + + /** + * Set a value of an arbitrary type to a specified position. The type of + * the value is inferred from the value passed to this method. The new + * value will overwrite an existing value at the specified position + * position if any. + * + *

The method will throw an std::out_of_range exception + * if the specified position is outside the current container range.

+ * + *

Calling this method will not change the size of the container.

+ * + * @param pos position to insert the value to. + * @param value value to insert. + * @return iterator position pointing to the block where the value is + * inserted. + */ + template + iterator set(size_type pos, const T& value); + + /** + * Set a value of an arbitrary type to a specified position. The type of + * the value is inferred from the value passed to this method. The new + * value will overwrite an existing value at the specified position + * position if any. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the + * right block to insert the value into. The other variant that doesn't + * take an iterator always starts the block lookup from the first block, + * which does not scale well as the block size grows.

+ * + *

This position hint iterator must precede the insertion + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception + * if the specified position is outside the current container range.

+ * + *

Calling this method will not change the size of the container.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right block + * to insert the value into. + * @param pos position to insert the value to. + * @param value value to insert. + * @return iterator position pointing to the block where the value is + * inserted. + */ + template + iterator set(const iterator& pos_hint, size_type pos, const T& value); + + /** + * Set multiple values of identical type to a range of elements starting + * at specified position. Any existing values will be overwritten by the + * new values. + * + *

The method will throw an std::out_of_range exception if + * the range of new values would fall outside the current container + * range.

+ * + *

Calling this method will not change the size of the container.

+ * + * @param pos position of the first value of the series of new values + * being inserted. + * @param it_begin iterator that points to the begin position of the + * values being set. + * @param it_end iterator that points to the end position of the values + * being set. + * @return iterator position pointing to the block where the value is + * inserted. When no value insertion occurs because the value set + * is empty, the end iterator position is returned. + */ + template + iterator set(size_type pos, const T& it_begin, const T& it_end); + + /** + * Set multiple values of identical type to a range of elements starting + * at specified position. Any existing values will be overwritten by the + * new values. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the first + * insertion block. The other variant that doesn't take an iterator + * always starts the block lookup from the first block, which does not + * scale well as the block size grows.

+ * + *

This position hint iterator must precede the insertion + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception if + * the range of new values would fall outside the current container + * range.

+ * + *

Calling this method will not change the size of the container.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right block + * to insert the value into. + * @param pos position of the first value of the series of new values + * being inserted. + * @param it_begin iterator that points to the begin position of the + * values being set. + * @param it_end iterator that points to the end position of the values + * being set. + * @return iterator position pointing to the block where the value is + * inserted. When no value insertion occurs because the value set + * is empty, the end iterator position is returned. + */ + template + iterator set(const iterator& pos_hint, size_type pos, const T& it_begin, const T& it_end); + + /** + * Append a new value to the end of the container. + * + * @param value new value to be appended to the end of the container. + * + * @return iterator position pointing to the block where the value is + * appended, which in this case is always the last block of the + * container. + */ + template + iterator push_back(const T& value); + + /** + * Append a new empty element to the end of the container. + * + * @return iterator position pointing to the block where the new empty + * element is appended, which in this case is always the last + * block of the container. + */ + iterator push_back_empty(); + + /** + * Insert multiple values of identical type to a specified position. + * Existing values that occur at or below the specified position will get + * shifted after the insertion. No existing values will be overwritten by + * the inserted values. + * + *

The method will throw an std::out_of_range exception + * if the specified position is outside the current container range.

+ * + *

Calling this method will increase the size of the container by + * the length of the new values inserted.

+ * + * @param pos position at which the new values are to be inserted. + * @param it_begin iterator that points to the begin position of the + * values being inserted. + * @param it_end iterator that points to the end position of the values + * being inserted. + * @return iterator position pointing to the block where the value is + * inserted. When no value insertion occurs because the value set + * is empty, the end iterator position is returned. + */ + template + iterator insert(size_type pos, const T& it_begin, const T& it_end); + + /** + * Insert multiple values of identical type to a specified position. + * Existing values that occur at or below the specified position will get + * shifted after the insertion. No existing values will be overwritten by + * the inserted values. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the first + * insertion block. The other variant that doesn't take an iterator + * always starts the block lookup from the first block, which does not + * scale well as the block size grows.

+ * + *

This position hint iterator must precede the insertion + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception + * if the specified position is outside the current container range.

+ * + *

Calling this method will increase the size of the container by + * the length of the new values inserted.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right block + * to insert the value into. + * @param pos position at which the new values are to be inserted. + * @param it_begin iterator that points to the begin position of the + * values being inserted. + * @param it_end iterator that points to the end position of the values + * being inserted. + * @return iterator position pointing to the block where the value is + * inserted. When no value insertion occurs because the value set + * is empty, the end iterator position is returned. + */ + template + iterator insert(const iterator& pos_hint, size_type pos, const T& it_begin, const T& it_end); + + /** + * Get the type of an element at specified position. + * + * @param pos position of the element. + * + * @return element type. + */ + mtv::element_t get_type(size_type pos) const; + + /** + * Check if element at specified position is empty of not. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container + * range.

+ * + * @param pos position of the element to check. + * + * @return true if the element is empty, false otherwise. + */ + bool is_empty(size_type pos) const; + + /** + * Set specified range of elements to be empty. Any existing values will + * be overwritten. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container size.

+ * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @return iterator position pointing to the block where the elements are + * emptied. + */ + iterator set_empty(size_type start_pos, size_type end_pos); + + /** + * Set specified range of elements to be empty. Any existing values will + * be overwritten. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the first block + * to empty. The other variant that doesn't take an iterator always + * starts the block lookup from the first block, which does not + * scale well as the block size grows.

+ * + *

This position hint iterator must precede the start + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container size.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right + * blocks to empty. + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @return iterator position pointing to the block where the elements are + * emptied. + */ + iterator set_empty(const iterator& pos_hint, size_type start_pos, size_type end_pos); + + /** + * Erase elements located between specified start and end positions. The + * end positions are both inclusive. Those elements originally located + * after the specified end position will get shifted up after the erasure. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container range.

+ * + *

Calling this method will decrease the size of the container by + * the length of the erased range.

+ * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + */ + void erase(size_type start_pos, size_type end_pos); + + /** + * Insert a range of empty elements at specified position. Those elements + * originally located after the insertion position will get shifted down + * after the insertion. + * + *

The method will throw an std::out_of_range exception if + * either the specified position is outside the current container + * range.

+ * + *

Calling this method will increase the size of the container by + * the length of the inserted empty elements.

+ * + * @param pos position at which to insert a range of empty elements. + * @param length number of empty elements to insert. + * @return iterator position pointing to the block where the empty range + * is inserted. When no insertion occurs because the length is + * zero, the end iterator position is returned. + */ + iterator insert_empty(size_type pos, size_type length); + + /** + * Insert a range of empty elements at specified position. Those elements + * originally located after the insertion position will get shifted down + * after the insertion. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the block in + * which to insert the new empty segment. The other variant that doesn't + * take an iterator always starts the block lookup from the first block, + * which does not scale well as the block size grows.

+ * + *

This position hint iterator must precede the start + * position to yield any performance benefit.

+ * + *

The caller is responsible for ensuring that the passed iterator is + * valid. The behavior of this method when passing an invalid iterator is + * undefined.

+ * + *

The method will throw an std::out_of_range exception if + * either the specified position is outside the current container + * range.

+ * + *

Calling this method will increase the size of the container by + * the length of the inserted empty elements.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right block + * in which to insert the empty segment. + * @param pos position at which to insert a range of empty elements. + * @param length number of empty elements to insert. + * @return iterator position pointing to the block where the empty range + * is inserted. When no insertion occurs because the length is + * zero, the end iterator position is returned. + */ + iterator insert_empty(const iterator& pos_hint, size_type pos, size_type length); + + /** + * Clear the content of the container. The size of the container will + * become zero after calling this method. + */ + void clear(); + + /** + * Return the current container size. + * + * @return current container size. + */ + size_type size() const; + + /** + * Return the current number of blocks in the primary array. Each + * non-empty block stores a secondary block that stores elements in a + * contiguous memory region (element block) and the number of elements it + * stores. An empty block only stores its logical size and does not store + * an actual element block. + * + *

For instance, if the container stores values of double-precision + * type at rows 0 to 2, values of std::string type at 3 to 7, and empty + * values at 8 to 10, it would consist of three blocks: one that stores + * double values, one that stores std::string values, and one that + * represents the empty value range in this exact order. In this specific + * scenario, block_size() returns 3, and size() + * returns 11.

+ * + * @return current number of blocks in the primary array. + */ + size_type block_size() const; + + /** + * Return whether or not the container is empty. + * + * @return true if the container is empty, false otherwise. + */ + bool empty() const; + + /** + * Get the value of an element at specified position. The caller must + * pass a variable of the correct type to store the value. + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element value to retrieve. + * @param value (out) variable to store the retrieved value. + */ + template + void get(size_type pos, T& value) const; + + /** + * Get the value of an element at specified position. The caller must + * specify the type of the element as the template parameter e.g. + * get(1). + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element value to retrieve. + * @return element value. + */ + template + T get(size_type pos) const; + + /** + * Return the value of an element at specified position and set that + * position empty. If the element resides in a managed element block, + * this call will release that element from that block. If the element is + * on a non-managed element block, this call is equivalent to + * set_empty(pos, pos). + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element to release. + * + * @return element value. + */ + template + T release(size_type pos); + + /** + * Retrieve the value of an element at specified position and set that + * position empty. If the element resides in a managed element block, + * this call will release that element from that block. If the element is + * on a non-managed element block, this call is equivalent to + * set_empty(pos, pos). + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos position of the element to release. + * @param value element value. + * + * @return iterator referencing the block where the position of the + * released element is. + */ + template + iterator release(size_type pos, T& value); + + /** + * Retrieve the value of an element at specified position and set that + * position empty. If the element resides in a managed element block, + * this call will release that element from that block. If the element is + * on a non-managed element block, this call is equivalent to + * set_empty(pos, pos). + * + *

The method will throw an std::out_of_range exception if + * the specified position is outside the current container range.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the block where + * the element resides. + * @param pos position of the element to release. + * @param value element value. + * + * @return iterator referencing the block where the position of the + * released element is. + */ + template + iterator release(const iterator& pos_hint, size_type pos, T& value); + + /** + * Release all its elements, and empties its content. Calling this method + * relinquishes the ownership of all elements stored in managed element + * blocks if any. + * + *

This call is equivalent of clear() if the container consists of no + * managed element blocks.

+ */ + void release(); + + /** + * Make all elements within specified range empty, and relinquish the + * ownership of the elements in that range. All elements in the managed + * element blocks within the range will be released and the container will + * no longer manage their life cycles after the call. + * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container size.

+ * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @return iterator position pointing to the block where the elements are + * released. + */ + iterator release_range(size_type start_pos, size_type end_pos); + + /** + * Make all elements within specified range empty, and relinquish the + * ownership of the elements in that range. All elements in the managed + * element blocks within the range will be released and the container will + * no longer manage their life cycles after the call. + * + *

This variant takes an iterator as an additional parameter, which is + * used as a block position hint to speed up the lookup of the first block + * to empty. The other variant that doesn't take an iterator always + * starts the block lookup from the first block, which does not + * scale well as the block size grows.

+ * + *

The method will throw an std::out_of_range exception if + * either the starting or the ending position is outside the current + * container size.

+ * + * @param pos_hint iterator used as a block position hint, to specify + * which block to start when searching for the right + * blocks in which elements are to be released. + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @return iterator position pointing to the block where the elements are + * released. + */ + iterator release_range(const iterator& pos_hint, size_type start_pos, size_type end_pos); + + iterator begin(); + iterator end(); + + const_iterator begin() const; + const_iterator end() const; + + const_iterator cbegin() const; + const_iterator cend() const; + + reverse_iterator rbegin(); + reverse_iterator rend(); + + const_reverse_iterator rbegin() const; + const_reverse_iterator rend() const; + + const_reverse_iterator crbegin() const; + const_reverse_iterator crend() const; + + /** + * Extend or shrink the container. When extending the container, it + * appends a series of empty elements to the end. When shrinking, the + * elements at the end of the container get stripped off. + * + * @param new_size size of the container after the resize. + */ + void resize(size_type new_size); + + /** + * Swap the content with another container. + * + * @param other another container to swap content with. + */ + void swap(multi_type_vector& other); + + /** + * Swap a part of the content with another instance. + * + * @param start_pos starting position + * @param end_pos ending position, inclusive. + * @param other another instance to swap the content with. + * @param other_pos insertion position in the other instance. + */ + void swap(size_type start_pos, size_type end_pos, multi_type_vector& other, size_type other_pos); + + /** + * Trim excess capacity from all non-empty blocks. + */ + void shrink_to_fit(); + + bool operator==(const multi_type_vector& other) const; + bool operator!=(const multi_type_vector& other) const; + + multi_type_vector& operator=(const multi_type_vector& other); + multi_type_vector& operator=(multi_type_vector&& other); + + /** + * Return the numerical identifier that represents passed element. + * + * @param elem element value. + * + * @return numerical identifier representing the element. + */ + template + static mtv::element_t get_element_type(const T& elem); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + void dump_blocks(std::ostream& os) const; + + void check_block_integrity() const; +#endif + +private: + void delete_element_block(size_type block_index); + + void delete_element_blocks(size_type start, size_type end); + + template + void get_impl(size_type pos, T& value) const; + + template + bool set_cells_precheck(size_type row, const T& it_begin, const T& it_end, size_type& end_pos); + + template + iterator set_impl(size_type pos, size_type block_index, const T& value); + + template + iterator release_impl(size_type pos, size_type block_index, T& value); + + void swap_impl( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index1, + size_type block_index2, size_type dblock_index1, size_type dblock_index2); + + void swap_single_block( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index, + size_type other_block_index); + + void swap_single_to_multi_blocks( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index, + size_type dst_block_index1, size_type dst_block_index2); + + void swap_multi_to_multi_blocks( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index1, + size_type block_index2, size_type dblock_index1, size_type dblock_index2); + + template + iterator insert_cells_impl(size_type row, size_type block_index, const T& it_begin, const T& it_end); + + void resize_impl(size_type new_size); + + /** + * Elements to transfer to the other container span across multiple + * blocks. + */ + iterator transfer_multi_blocks( + size_type start_pos, size_type end_pos, size_type block_index1, size_type block_index2, multi_type_vector& dest, + size_type dest_pos); + + /** + * @param start_pos logical start position. + * @param end_pos logical end position. + * @param block_index1 index of the first block + * @param overwrite when true, and when the stored values are pointers to + * heap objects, objects pointed to by the overwritten + * pointers should be freed from memory. + */ + iterator set_empty_impl(size_type start_pos, size_type end_pos, size_type block_index1, bool overwrite); + + iterator set_empty_in_single_block(size_type start_row, size_type end_row, size_type block_index, bool overwrite); + + /** + * @param start_row logical start position. + * @param end_row logical end position. + * @param block_index1 index of the first block. + * @param block_index2 index of the last block. + * @param overwrite when true, and when the stored values are pointers to + * heap objects, objects pointed to by the overwritten + * pointers should be freed from memory. + */ + iterator set_empty_in_multi_blocks( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, bool overwrite); + + void erase_impl(size_type start_pos, size_type end_pos); + void erase_in_single_block(size_type start_pos, size_type end_pos, size_type block_index); + + /** + * @param pos logical position at which to insert an empty segment. + * @param block_index index of the block. + * @param length length of the emtpy segment to insert. + */ + iterator insert_empty_impl(size_type pos, size_type block_index, size_type length); + + void insert_blocks_at(size_type position, size_type insert_pos, blocks_type& new_blocks); + + void prepare_blocks_to_transfer( + blocks_to_transfer& bucket, size_type block_index1, size_type offset1, size_type block_index2, + size_type offset2); + + iterator set_whole_block_empty(size_type block_index, bool overwrite); + + template + iterator push_back_impl(const T& value); + + template + iterator set_cells_impl( + size_type row, size_type end_row, size_type block_index1, const T& it_begin, const T& it_end); + + template + iterator set_cells_to_single_block( + size_type start_row, size_type end_row, size_type block_index, const T& it_begin, const T& it_end); + + template + iterator set_cells_to_multi_blocks( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end); + + template + iterator set_cells_to_multi_blocks_block1_non_equal( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end); + + template + iterator set_cells_to_multi_blocks_block1_non_empty( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end); + + template + iterator set_cell_to_empty_block(size_type block_index, size_type pos_in_block, const T& cell); + + template + iterator set_cell_to_non_empty_block_of_size_one(size_type block_index, const T& cell); + + /** + * Find the correct block position for a given logical row ID. + * + * @param row logical position of an element. + * @param start_block_index index of the first block to start the search + * from. + * + * @return index of the block that contains the specified logical row ID. + */ + size_type get_block_position(size_type row, size_type start_block_index = 0) const; + + /** + * Same as above, but try to infer block position from the private position + * data stored in the iterator first before trying full search. + */ + size_type get_block_position(const typename value_type::private_data& pos_data, size_type row) const; + + template + void create_new_block_with_new_cell(size_type block_index, const T& cell); + + template + void append_cell_to_block(size_type block_index, const T& cell); + + /** + * Try to append a sequence of values to the previous block if the previous + * block exists and is of the same type as the new values. + * + * @return true if the values have been appended successfully, otherwise + * false. + */ + template + bool append_to_prev_block( + size_type block_index, element_category_type cat, size_type length, const T& it_begin, const T& it_end); + + template + void insert_cells_to_middle(size_type row, size_type block_index, const T& it_begin, const T& it_end); + + template + iterator set_cell_to_middle_of_block(size_type block_index, size_type pos_in_block, const T& cell); + + /** + * Set a new value to the top of specified non-empty block. The block is + * expected to be of size greater than one, and the previous block is not of + * the same type as the value being inserted. + */ + template + void set_cell_to_top_of_data_block(size_type block_index, const T& cell); + + template + void set_cell_to_bottom_of_data_block(size_type block_index, const T& cell); + + iterator transfer_impl( + size_type start_pos, size_type end_pos, size_type block_index1, multi_type_vector& dest, size_type dest_pos); + + /** + * All elements to transfer to the other instance is in the same block. + */ + iterator transfer_single_block( + size_type start_pos, size_type end_pos, size_type block_index1, multi_type_vector& dest, size_type dest_pos); + + /** + * Merge with previous or next block as needed. + * + * @param block_index index of the block that may need merging. + * + * @return size of previous block if the block is merged with the previous + * block, or 0 if it didn't merge with the previous block. + */ + size_type merge_with_adjacent_blocks(size_type block_index); + + /** + * Merge only with the next block if the two are of the same type. + * + * @param block_index index of the block that may need merging. + * + * @return true if merge occurs, false otherwise. + */ + bool merge_with_next_block(size_type block_index); + + /** + * Set a new block in the middle of an existing block. This call inserts + * two new blocks below the specificed block position. The first one will + * be empty, and the second one will contain the lower elements of the + * existing block. + * + * @param block_index index of block into which to set a new block. + * @param offset position in the existing block to set the new block to. + * @param new_block_size size of the new block + * @param overwrite whether or not to overwrite the elements replaced by + * the new block. + * @return index of the inserted middle block. + */ + size_type set_new_block_to_middle( + size_type block_index, size_type offset, size_type new_block_size, bool overwrite); + + /** + * Check if the previous block is of specified type, if exists. + * + * @param block_index index of the current block. + * @param cat desired block type. + * + * @return true if the previous block exists and it's of specified type, + * otherwise false. + */ + bool is_previous_block_of_type(size_type block_index, element_category_type cat) const; + + /** + * Check if the next block is of specified type, if exists. + * + * @param block_index index of the current block. + * @param cat desired block type. + * + * @return true if the next block exists and it's of specified type, + * otherwise false. + */ + bool is_next_block_of_type(size_type block_index, element_category_type cat) const; + + /** + * Send elements from a source block to place them in a destination block. + * In return, the method returns the elements in the destination block + * that have been replaced by the elements sent by the caller. The caller + * needs to manage the life cycle of the returned block. + * + * Note that the destination block is expected to be non-empty. This also + * implies that the returned block is never null and always contains + * elements. + * + * @param src_data source data block from which the elements are sent. + * @param src_offset position of the first element in the source block. + * @param dst_index destination block index. + * @param dst_offset position in the destination block where the sent + * elements are to be placed. + * @param len length of elements. + * + * @return heap allocated block that contains the overwritten elements + * originally in the destination block. The caller needs to manage + * its life cycle. + */ + element_block_type* exchange_elements( + const element_block_type& src_data, size_type src_offset, size_type dst_index, size_type dst_offset, + size_type len); + + void exchange_elements( + const element_block_type& src_blk, size_type src_offset, size_type dst_index1, size_type dst_offset1, + size_type dst_index2, size_type dst_offset2, size_type len, blocks_type& new_blocks); + + bool append_empty(size_type len); + + inline iterator get_iterator(size_type block_index) + { + auto iter_pos = m_block_store.positions.begin(); + std::advance(iter_pos, block_index); + auto iter_size = m_block_store.sizes.begin(); + std::advance(iter_size, block_index); + auto iter_elem = m_block_store.element_blocks.begin(); + std::advance(iter_elem, block_index); + + return iterator( + {iter_pos, iter_size, iter_elem}, + {m_block_store.positions.end(), m_block_store.sizes.end(), m_block_store.element_blocks.end()}, this, + block_index); + } + + inline const_iterator get_const_iterator(size_type block_index) const + { + auto iter_pos = m_block_store.positions.cbegin(); + std::advance(iter_pos, block_index); + auto iter_size = m_block_store.sizes.cbegin(); + std::advance(iter_size, block_index); + auto iter_elem = m_block_store.element_blocks.cbegin(); + std::advance(iter_elem, block_index); + + return const_iterator( + {iter_pos, iter_size, iter_elem}, + {m_block_store.positions.cend(), m_block_store.sizes.cend(), m_block_store.element_blocks.cend()}, this, + block_index); + } + +private: + using adjust_block_positions_func = detail::adjust_block_positions; + + event_func m_hdl_event; + blocks_type m_block_store; + size_type m_cur_size; + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + mutable int m_trace_call_depth = 0; +#endif +}; + +}}} // namespace mdds::mtv::soa + +#include "main_def.inl" + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/soa/main_def.inl b/include/mdds/multi_type_vector/soa/main_def.inl new file mode 100644 index 0000000..de3f520 --- /dev/null +++ b/include/mdds/multi_type_vector/soa/main_def.inl @@ -0,0 +1,5238 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#include "../env.hpp" +#include "../util.hpp" +#if MDDS_MTV_USE_STANDARD_ELEMENT_BLOCKS +#include "../standard_element_blocks.hpp" +#endif + +namespace mdds { namespace mtv { namespace soa { + +namespace detail { + +template +void erase(VecT& arr, SizeT index, SizeT size) +{ + auto it = arr.begin() + index; + arr.erase(it, it + size); +} + +} // namespace detail + +template +multi_type_vector::blocks_type::blocks_type() +{} + +template +multi_type_vector::blocks_type::blocks_type(const blocks_type& other) + : positions(other.positions), sizes(other.sizes), element_blocks(other.element_blocks) +{ + for (element_block_type*& data : element_blocks) + { + if (data) + data = block_funcs::clone_block(*data); + } +} + +template +multi_type_vector::blocks_type::blocks_type(blocks_type&& other) + : positions(std::move(other.positions)), sizes(std::move(other.sizes)), + element_blocks(std::move(other.element_blocks)) +{} + +template +void multi_type_vector::blocks_type::erase(size_type index) +{ + positions.erase(positions.begin() + index); + sizes.erase(sizes.begin() + index); + element_blocks.erase(element_blocks.begin() + index); +} + +template +void multi_type_vector::blocks_type::erase(size_type index, size_type size) +{ + detail::erase(positions, index, size); + detail::erase(sizes, index, size); + detail::erase(element_blocks, index, size); +} + +template +void multi_type_vector::blocks_type::insert(size_type index, size_type size) +{ + positions.insert(positions.begin() + index, size, 0); + sizes.insert(sizes.begin() + index, size, 0); + element_blocks.insert(element_blocks.begin() + index, size, nullptr); +} + +template +void multi_type_vector::blocks_type::insert( + size_type index, size_type pos, size_type size, element_block_type* data) +{ + positions.insert(positions.begin() + index, pos); + sizes.insert(sizes.begin() + index, size); + element_blocks.insert(element_blocks.begin() + index, data); +} + +template +void multi_type_vector::blocks_type::insert(size_type index, const blocks_type& new_blocks) +{ + positions.insert(positions.begin() + index, new_blocks.positions.begin(), new_blocks.positions.end()); + sizes.insert(sizes.begin() + index, new_blocks.sizes.begin(), new_blocks.sizes.end()); + element_blocks.insert( + element_blocks.begin() + index, new_blocks.element_blocks.begin(), new_blocks.element_blocks.end()); +} + +template +void multi_type_vector::blocks_type::calc_block_position(size_type index) +{ + if (index == 0) + { + positions[index] = 0; + return; + } + + assert(index < positions.size()); + positions[index] = positions[index - 1] + sizes[index - 1]; +} + +template +typename multi_type_vector::size_type multi_type_vector::blocks_type::calc_next_block_position( + size_type index) +{ + return positions[index] + sizes[index]; +} + +template +void multi_type_vector::blocks_type::swap(size_type index1, size_type index2) +{ + std::swap(positions[index1], positions[index2]); + std::swap(sizes[index1], sizes[index2]); + std::swap(element_blocks[index1], element_blocks[index2]); +} + +template +void multi_type_vector::blocks_type::swap(blocks_type& other) +{ + positions.swap(other.positions); + sizes.swap(other.sizes); + element_blocks.swap(other.element_blocks); +} + +template +void multi_type_vector::blocks_type::reserve(size_type n) +{ + positions.reserve(n); + sizes.reserve(n); + element_blocks.reserve(n); +} + +template +bool multi_type_vector::blocks_type::equals(const blocks_type& other) const +{ + if (positions != other.positions) + return false; + + if (sizes != other.sizes) + return false; + + if (element_blocks.size() != other.element_blocks.size()) + return false; + + auto it2 = other.element_blocks.cbegin(); + + for (const element_block_type* data1 : element_blocks) + { + const element_block_type* data2 = *it2++; + + if (data1) + { + if (!data2) + // left is non-empty while right is empty. + return false; + } + else + { + if (data2) + // left is empty while right is non-empty. + return false; + } + + if (!data1) + { + // Both are empty blocks. + assert(!data2); + continue; + } + + assert(data1 && data2); + if (!block_funcs::equal_block(*data1, *data2)) + return false; + } + + return true; +} + +template +void multi_type_vector::blocks_type::clear() +{ + positions.clear(); + sizes.clear(); + element_blocks.clear(); +} + +template +void multi_type_vector::blocks_type::check_integrity() const +{ + if (positions.size() != sizes.size()) + throw mdds::integrity_error("position and size arrays are of different sizes!"); + + if (positions.size() != element_blocks.size()) + throw mdds::integrity_error("position and element-block arrays are of different sizes!"); +} + +template +typename multi_type_vector::position_type multi_type_vector::next_position(const position_type& pos) +{ + position_type ret = pos; + if (pos.second + 1 < pos.first->size) + { + // Next element is still in the same block. + ++ret.second; + } + else + { + ++ret.first; + ret.second = 0; + } + + return ret; +} + +template +typename multi_type_vector::position_type multi_type_vector::advance_position( + const position_type& pos, int steps) +{ + return mdds::mtv::detail::advance_position(pos, steps); +} + +template +typename multi_type_vector::const_position_type multi_type_vector::next_position( + const const_position_type& pos) +{ + const_position_type ret = pos; + if (pos.second + 1 < pos.first->size) + { + // Next element is still in the same block. + ++ret.second; + } + else + { + ++ret.first; + ret.second = 0; + } + + return ret; +} + +template +typename multi_type_vector::const_position_type multi_type_vector::advance_position( + const const_position_type& pos, int steps) +{ + return mdds::mtv::detail::advance_position(pos, steps); +} + +template +typename multi_type_vector::size_type multi_type_vector::logical_position( + const const_position_type& pos) +{ + return pos.first->position + pos.second; +} + +template +template +typename _Blk::value_type multi_type_vector::get(const const_position_type& pos) +{ + return mdds::mtv::detail::get_block_element_at<_Blk>(*pos.first->data, pos.second); +} + +template +typename multi_type_vector::event_func& multi_type_vector::event_handler() +{ + MDDS_MTV_TRACE(accessor); + + return m_hdl_event; +} + +template +const typename multi_type_vector::event_func& multi_type_vector::event_handler() const +{ + MDDS_MTV_TRACE(accessor); + + return m_hdl_event; +} + +template +multi_type_vector::multi_type_vector() : m_cur_size(0) +{ + MDDS_MTV_TRACE(constructor); +} + +template +multi_type_vector::multi_type_vector(const event_func& hdl) : m_hdl_event(hdl), m_cur_size(0) +{ + MDDS_MTV_TRACE_ARGS(constructor, "event_func=?"); +} + +template +multi_type_vector::multi_type_vector(event_func&& hdl) : m_hdl_event(std::move(hdl)), m_cur_size(0) +{ + MDDS_MTV_TRACE_ARGS(constructor, "event_func=? (move)"); +} + +template +multi_type_vector::multi_type_vector(size_type init_size) : m_cur_size(init_size) +{ + MDDS_MTV_TRACE_ARGS(constructor, "init_size=" << init_size); + + if (!init_size) + return; + + // Initialize with an empty block that spans from 0 to max. + m_block_store.positions.emplace_back(0); + m_block_store.sizes.emplace_back(init_size); + m_block_store.element_blocks.emplace_back(nullptr); +} + +template +template +multi_type_vector::multi_type_vector(size_type init_size, const T& value) : m_cur_size(init_size) +{ + MDDS_MTV_TRACE_ARGS( + constructor, "init_size=" << init_size << "; value=? (type=" << mdds_mtv_get_element_type(value) << ")"); + + if (!init_size) + return; + + element_block_type* data = mdds_mtv_create_new_block(init_size, value); + m_hdl_event.element_block_acquired(data); + m_block_store.positions.emplace_back(0); + m_block_store.sizes.emplace_back(init_size); + m_block_store.element_blocks.emplace_back(data); +} + +template +template +multi_type_vector::multi_type_vector(size_type init_size, const T& it_begin, const T& it_end) + : m_cur_size(init_size) +{ + MDDS_MTV_TRACE_ARGS( + constructor, + "init_size=" << init_size << "; it_begin=?; it_end=? (length=" << std::distance(it_begin, it_end) << ")"); + + if (!m_cur_size) + return; + + size_type data_len = std::distance(it_begin, it_end); + if (m_cur_size != data_len) + throw mdds::invalid_arg_error("Specified size does not match the size of the initial data array."); + + element_block_type* data = mdds_mtv_create_new_block(*it_begin, it_begin, it_end); + m_hdl_event.element_block_acquired(data); + m_block_store.positions.emplace_back(0); + m_block_store.sizes.emplace_back(m_cur_size); + m_block_store.element_blocks.emplace_back(data); +} + +template +multi_type_vector::multi_type_vector(const multi_type_vector& other) + : m_hdl_event(other.m_hdl_event), m_block_store(other.m_block_store), m_cur_size(other.m_cur_size) +{ + MDDS_MTV_TRACE_ARGS(constructor, "other=? (copy)"); + + for (const element_block_type* data : m_block_store.element_blocks) + { + if (data) + m_hdl_event.element_block_acquired(data); + } + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in copy construction" << std::endl; + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +multi_type_vector::multi_type_vector(multi_type_vector&& other) + : m_hdl_event(std::move(other.m_hdl_event)), m_block_store(std::move(other.m_block_store)), + m_cur_size(other.m_cur_size) +{ + MDDS_MTV_TRACE_ARGS(constructor, "other=? (move)"); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in copy construction" << std::endl; + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +multi_type_vector::~multi_type_vector() +{ + MDDS_MTV_TRACE(destructor); + + delete_element_blocks(0, m_block_store.positions.size()); +} + +template +void multi_type_vector::delete_element_block(size_type block_index) +{ + element_block_type* data = m_block_store.element_blocks[block_index]; + if (!data) + // This block is empty. + return; + + m_hdl_event.element_block_released(data); + block_funcs::delete_block(data); + m_block_store.element_blocks[block_index] = nullptr; +} + +template +void multi_type_vector::delete_element_blocks(size_type start, size_type end) +{ + for (size_type i = start; i < end; ++i) + delete_element_block(i); +} + +template +template +void multi_type_vector::get_impl(size_type pos, T& value) const +{ + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::get", __LINE__, pos, block_size(), size()); + + assert(block_index < m_block_store.element_blocks.size()); + const element_block_type* data = m_block_store.element_blocks[block_index]; + if (!data) + { + // empty cell block. + mdds_mtv_get_empty_value(value); + return; + } + + size_type start_row = m_block_store.positions[block_index]; + assert(pos >= start_row); + size_type offset = pos - start_row; + mdds_mtv_get_value(*data, offset, value); +} + +template +typename multi_type_vector::position_type multi_type_vector::position(size_type pos) +{ + MDDS_MTV_TRACE_ARGS(accessor, "pos=" << pos); + + if (pos == m_cur_size) + { + // This is a valid end position. Create a valid position object that + // represents a valid end position. + return position_type(end(), 0); + } + + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::position", __LINE__, pos, block_size(), size()); + + size_type start_pos = m_block_store.positions[block_index]; + + iterator it = get_iterator(block_index); + return position_type(it, pos - start_pos); +} + +template +typename multi_type_vector::position_type multi_type_vector::position( + const iterator& pos_hint, size_type pos) +{ + MDDS_MTV_TRACE_ARGS(accessor_with_pos_hint, "pos_hint=" << pos_hint << "; pos=" << pos); + + if (pos == m_cur_size) + { + // This is a valid end position. Create a valid position object that + // represents a valid end position. + return position_type(end(), 0); + } + + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::position", __LINE__, pos, block_size(), size()); + + iterator it = get_iterator(block_index); + size_type start_pos = m_block_store.positions[block_index]; + return position_type(it, pos - start_pos); +} + +template +typename multi_type_vector::const_position_type multi_type_vector::position(size_type pos) const +{ + MDDS_MTV_TRACE_ARGS(accessor, "pos=" << pos); + + if (pos == m_cur_size) + { + // This is a valid end position. Create a valid position object that + // represents a valid end position. + return const_position_type(cend(), 0); + } + + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::position", __LINE__, pos, block_size(), size()); + + size_type start_pos = m_block_store.positions[block_index]; + + const_iterator it = get_const_iterator(block_index); + return const_position_type(it, pos - start_pos); +} + +template +typename multi_type_vector::const_position_type multi_type_vector::position( + const const_iterator& pos_hint, size_type pos) const +{ + MDDS_MTV_TRACE_ARGS(accessor_with_pos_hint, "pos_hint=" << pos_hint << "; pos=" << pos); + + if (pos == m_cur_size) + { + // This is a valid end position. Create a valid position object that + // represents a valid end position. + return const_position_type(cend(), 0); + } + + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::position", __LINE__, pos, block_size(), size()); + + const_iterator it = get_const_iterator(block_index); + size_type start_pos = m_block_store.positions[block_index]; + return const_position_type(it, pos - start_pos); +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer( + size_type start_pos, size_type end_pos, multi_type_vector& dest, size_type dest_pos) +{ + MDDS_MTV_TRACE_ARGS( + mutator, "start_pos=" << start_pos << "; end_pos=" << end_pos << "; dest=?; dest_pos=" << dest_pos); + + if (&dest == this) + throw invalid_arg_error("You cannot transfer between the same container."); + + size_type block_index1 = get_block_position(start_pos); + if (block_index1 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::transfer", __LINE__, start_pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block, os_prev_block_dest; + dump_blocks(os_prev_block); + dest.dump_blocks(os_prev_block_dest); +#endif + + iterator ret = transfer_impl(start_pos, end_pos, block_index1, dest, dest_pos); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_block, os_block_dest; + dump_blocks(os_block); + dest.dump_blocks(os_block_dest); + + try + { + check_block_integrity(); + dest.check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << std::endl + << "block integrity check failed in transfer (start_pos=" << start_pos << "; end_pos=" << end_pos + << "; dest_pos=" << dest_pos << ")" << std::endl; + os << std::endl << "previous block state (source):" << std::endl; + os << os_prev_block.str(); + os << std::endl << "previous block state (destination):" << std::endl; + os << os_prev_block_dest.str(); + os << std::endl << "altered block state (source):" << std::endl; + os << os_block.str(); + os << std::endl << "altered block state (destination):" << std::endl; + os << os_block_dest.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer( + const iterator& pos_hint, size_type start_pos, size_type end_pos, multi_type_vector& dest, size_type dest_pos) +{ + MDDS_MTV_TRACE_ARGS( + mutator_with_pos_hint, "pos_hint=" << pos_hint << "; start_pos=" << start_pos << "; end_pos=" << end_pos + << "; dest=?; dest_pos=" << dest_pos); + + if (&dest == this) + throw invalid_arg_error("You cannot transfer between the same container."); + + size_type block_index1 = get_block_position(pos_hint->__private_data, start_pos); + if (block_index1 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::transfer", __LINE__, start_pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block, os_prev_block_dest; + dump_blocks(os_prev_block); + dest.dump_blocks(os_prev_block_dest); +#endif + + iterator ret = transfer_impl(start_pos, end_pos, block_index1, dest, dest_pos); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_block, os_block_dest; + dump_blocks(os_block); + dest.dump_blocks(os_block_dest); + + try + { + check_block_integrity(); + dest.check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << std::endl + << "block integrity check failed in transfer (start_pos=" << start_pos << "; end_pos=" << end_pos + << "; dest_pos=" << dest_pos << ")" << std::endl; + os << std::endl << "previous block state (source):" << std::endl; + os << os_prev_block.str(); + os << std::endl << "previous block state (destination):" << std::endl; + os << os_prev_block_dest.str(); + os << std::endl << "altered block state (source):" << std::endl; + os << os_block.str(); + os << std::endl << "altered block state (destination):" << std::endl; + os << os_block_dest.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set(size_type pos, const T& value) +{ + MDDS_MTV_TRACE_ARGS(mutator, "pos=" << pos << "; value=? (type=" << mdds_mtv_get_element_type(value) << ")"); + + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = set_impl(pos, block_index, value); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in set (pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set( + const iterator& pos_hint, size_type pos, const T& value) +{ + MDDS_MTV_TRACE_ARGS( + mutator_with_pos_hint, + "pos_hint=" << pos_hint << "; pos=" << pos << "; value=? (type=" << mdds_mtv_get_element_type(value) << ")"); + + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = set_impl(pos, block_index, value); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in set (pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set( + size_type pos, const T& it_begin, const T& it_end) +{ + MDDS_MTV_TRACE_ARGS( + mutator, "pos=" << pos << "; it_begin=?; it_end=? (length=" << std::distance(it_begin, it_end) << ")"); + + auto res = mdds::mtv::detail::calc_input_end_position(it_begin, it_end, pos, m_cur_size); + + if (!res.second) + return end(); + + size_type end_pos = res.first; + size_type block_index1 = get_block_position(pos); + + if (block_index1 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + auto ret = set_cells_impl(pos, end_pos, block_index1, it_begin, it_end); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in set (pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set( + const iterator& pos_hint, size_type pos, const T& it_begin, const T& it_end) +{ + MDDS_MTV_TRACE_ARGS( + mutator_with_pos_hint, "pos_hint=" << pos_hint << "; pos=" << pos << "; it_begin=?; it_end=? (length=" + << std::distance(it_begin, it_end) << ")"); + + auto res = mdds::mtv::detail::calc_input_end_position(it_begin, it_end, pos, m_cur_size); + if (!res.second) + return end(); + + size_type end_pos = res.first; + size_type block_index1 = get_block_position(pos_hint->__private_data, pos); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + auto ret = set_cells_impl(pos, end_pos, block_index1, it_begin, it_end); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in set (pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::push_back(const T& value) +{ + MDDS_MTV_TRACE_ARGS(mutator, "value=? (type=" << mdds_mtv_get_element_type(value) << ")"); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + auto ret = push_back_impl(value); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in push_back" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +typename multi_type_vector::iterator multi_type_vector::push_back_empty() +{ + MDDS_MTV_TRACE(mutator); + + size_type block_index = m_block_store.positions.size(); + + if (!append_empty(1)) + { + // Last empty block has been extended. + --block_index; + } + + // Get the iterator of the last block. + return get_iterator(block_index); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::insert( + size_type pos, const T& it_begin, const T& it_end) +{ + MDDS_MTV_TRACE_ARGS( + mutator, "pos=" << pos << "it_begin=?; it_end=? (length=" << std::distance(it_begin, it_end) << ")"); + + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::insert", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = insert_cells_impl(pos, block_index, it_begin, it_end); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + os << "block integrity check failed in insert (pos=" << pos + << "; value-size=" << std::distance(it_begin, it_end) << "; value-type=" << cat << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::insert( + const iterator& pos_hint, size_type pos, const T& it_begin, const T& it_end) +{ + MDDS_MTV_TRACE_ARGS( + mutator_with_pos_hint, "pos_hint=" << pos_hint << "; pos=" << pos << "; it_begin=?; it_end=? (length=" + << std::distance(it_begin, it_end) << ")"); + + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::insert", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = insert_cells_impl(pos, block_index, it_begin, it_end); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + os << "block integrity check failed in insert (pos=" << pos + << "; value-size=" << std::distance(it_begin, it_end) << "; value-type=" << cat << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::push_back_impl(const T& value) +{ + element_category_type cat = mdds_mtv_get_element_type(value); + element_block_type* last_data = + m_block_store.element_blocks.empty() ? nullptr : m_block_store.element_blocks.back(); + + if (!last_data || cat != get_block_type(*last_data)) + { + // Either there is no block, or the last block is empty or of different + // type. Append a new block. + size_type block_index = m_block_store.positions.size(); + size_type start_pos = m_cur_size; + + m_block_store.push_back(start_pos, 1, nullptr); + create_new_block_with_new_cell(block_index, value); + ++m_cur_size; + + return get_iterator(block_index); + } + + assert(last_data); + assert(cat == get_block_type(*last_data)); + + // Append the new value to the last block. + size_type block_index = m_block_store.positions.size() - 1; + + mdds_mtv_append_value(*last_data, value); + ++m_block_store.sizes.back(); + ++m_cur_size; + + return get_iterator(block_index); +} + +template +mtv::element_t multi_type_vector::get_type(size_type pos) const +{ + MDDS_MTV_TRACE_ARGS(accessor, "pos=" << pos); + + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::get_type", __LINE__, pos, block_size(), size()); + + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + if (!blk_data) + return mtv::element_type_empty; + + return mtv::get_block_type(*blk_data); +} + +template +bool multi_type_vector::is_empty(size_type pos) const +{ + MDDS_MTV_TRACE_ARGS(accessor, "pos=" << pos); + + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::is_empty", __LINE__, pos, block_size(), size()); + + return m_block_store.element_blocks[block_index] == nullptr; +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty( + size_type start_pos, size_type end_pos) +{ + MDDS_MTV_TRACE_ARGS(mutator, "start_pos=" << start_pos << "; end_pos=" << end_pos); + + size_type block_index1 = get_block_position(start_pos); + if (block_index1 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set_empty", __LINE__, start_pos, block_size(), size()); + + return set_empty_impl(start_pos, end_pos, block_index1, true); +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty( + const iterator& pos_hint, size_type start_pos, size_type end_pos) +{ + MDDS_MTV_TRACE_ARGS( + mutator_with_pos_hint, "pos_hint=" << pos_hint << "; start_pos=" << start_pos << "; end_pos=" << end_pos); + + size_type block_index1 = get_block_position(pos_hint->__private_data, start_pos); + if (block_index1 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set_empty", __LINE__, start_pos, block_size(), size()); + + return set_empty_impl(start_pos, end_pos, block_index1, true); +} + +template +void multi_type_vector::erase(size_type start_pos, size_type end_pos) +{ + MDDS_MTV_TRACE_ARGS(mutator, "start_pos=" << start_pos << "; end_pos=" << end_pos); + + if (start_pos > end_pos) + throw std::out_of_range("Start row is larger than the end row."); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + erase_impl(start_pos, end_pos); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in erase (" << start_pos << "-" << end_pos << ")" << std::endl; + os << "block integrity check failed in push_back" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +typename multi_type_vector::iterator multi_type_vector::insert_empty(size_type pos, size_type length) +{ + MDDS_MTV_TRACE_ARGS(mutator, "pos=" << pos << "; length=" << length); + + if (!length) + // Nothing to insert. + return end(); + + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::insert_empty", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = insert_empty_impl(pos, block_index, length); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in insert_empty (pos=" << pos << "; length=" << length << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +typename multi_type_vector::iterator multi_type_vector::insert_empty( + const iterator& pos_hint, size_type pos, size_type length) +{ + MDDS_MTV_TRACE_ARGS(mutator_with_pos_hint, "pos_hint=" << pos_hint << "; pos=" << pos << "; length=" << length); + + if (!length) + // Nothing to insert. + return end(); + + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::insert_empty", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret = insert_empty_impl(pos, block_index, length); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in insert_empty (pos=" << pos << "; length=" << length << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +void multi_type_vector::clear() +{ + MDDS_MTV_TRACE(mutator); + + delete_element_blocks(0, m_block_store.element_blocks.size()); + m_block_store.clear(); + m_cur_size = 0; +} + +template +template +bool multi_type_vector::set_cells_precheck( + size_type pos, const T& it_begin, const T& it_end, size_type& end_pos) +{ + size_type length = std::distance(it_begin, it_end); + if (!length) + // empty data array. nothing to do. + return false; + + end_pos = pos + length - 1; + if (end_pos >= m_cur_size) + throw std::out_of_range("Data array is too long."); + + return true; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_impl( + size_type pos, size_type block_index, const T& value) +{ + size_type start_row = m_block_store.positions[block_index]; + + size_type blk_size = m_block_store.sizes[block_index]; + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + assert(blk_size > 0); // block size should never be zero at any time. + + assert(pos >= start_row); + size_type pos_in_block = pos - start_row; + assert(pos_in_block < blk_size); + + if (!blk_data) + { + // This is an empty block. + return set_cell_to_empty_block(block_index, pos_in_block, value); + } + + element_category_type cat = mdds_mtv_get_element_type(value); + + assert(blk_data); + element_category_type blk_cat = mdds::mtv::get_block_type(*blk_data); + + if (blk_cat == cat) + { + // This block is of the same type as the cell being inserted. + size_type i = pos - start_row; + block_funcs::overwrite_values(*blk_data, i, 1); + mdds_mtv_set_value(*blk_data, i, value); + return get_iterator(block_index); + } + + assert(blk_cat != cat); + + if (pos == start_row) + { + // t|???|x--|???|b - Insertion point is at the start of the block. + if (blk_size == 1) + { + // t|???|x|???|b + return set_cell_to_non_empty_block_of_size_one(block_index, value); + } + + assert(blk_size > 1); + bool blk_prev = is_previous_block_of_type(block_index, cat); + if (blk_prev) + { + // t|xxx|x--|???|b - Append to the previous block. + m_block_store.sizes[block_index] -= 1; + m_block_store.positions[block_index] += 1; + block_funcs::overwrite_values(*m_block_store.element_blocks[block_index], 0, 1); + block_funcs::erase(*m_block_store.element_blocks[block_index], 0); + m_block_store.sizes[block_index - 1] += 1; + mdds_mtv_append_value(*m_block_store.element_blocks[block_index - 1], value); + return get_iterator(block_index - 1); + } + + // t|---|x--|???|b + set_cell_to_top_of_data_block(block_index, value); + return get_iterator(block_index); + } + + if (pos < (start_row + blk_size - 1)) + { + // t|???|-x-|???|b - Insertion point is in the middle of the block. + return set_cell_to_middle_of_block(block_index, pos_in_block, value); + } + + // t|???|--x|???|b - Insertion point is at the end of the block. + assert(pos == (start_row + m_block_store.sizes[block_index] - 1)); + assert(pos > start_row); + assert(m_block_store.sizes[block_index] > 1); + + if (block_index == 0) + { + if (m_block_store.positions.size() == 1) + { + // t|--x|b - This is the only block. + set_cell_to_bottom_of_data_block(0, value); + iterator itr = end(); + return --itr; + } + + bool blk_next = is_next_block_of_type(block_index, cat); + if (!blk_next) + { + // t|--x|---|b - Next block is of different type. + set_cell_to_bottom_of_data_block(0, value); + iterator itr = begin(); + return ++itr; + } + + // t|--x|xxx|b - Next block is of the same type as the new value. + + block_funcs::overwrite_values(*blk_data, blk_size - 1, 1); + block_funcs::erase(*blk_data, blk_size - 1); + m_block_store.sizes[block_index] -= 1; + mdds_mtv_prepend_value(*m_block_store.element_blocks[block_index + 1], value); + m_block_store.sizes[block_index + 1] += 1; + m_block_store.positions[block_index + 1] -= 1; + + return get_iterator(block_index + 1); + } + + assert(block_index > 0); + + if (block_index == m_block_store.positions.size() - 1) + { + // t|???|--x|b - This is the last block. + set_cell_to_bottom_of_data_block(block_index, value); + iterator itr = end(); + return --itr; + } + + bool blk_next = is_next_block_of_type(block_index, cat); + if (!blk_next) + { + // t|???|--x|---|b - Next block is of different type than the new + // value's. + set_cell_to_bottom_of_data_block(block_index, value); + return get_iterator(block_index + 1); + } + + // t|???|--x|xxx|b - The next block is of the same type as the new value. + block_funcs::overwrite_values(*blk_data, blk_size - 1, 1); + block_funcs::erase(*blk_data, blk_size - 1); + m_block_store.sizes[block_index] -= 1; + mdds_mtv_prepend_value(*m_block_store.element_blocks[block_index + 1], value); + m_block_store.sizes[block_index + 1] += 1; + m_block_store.positions[block_index + 1] -= 1; + + return get_iterator(block_index + 1); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::release_impl( + size_type pos, size_type block_index, T& value) +{ + const element_block_type* blk_data = m_block_store.element_blocks[block_index]; + size_type start_pos = m_block_store.positions[block_index]; + + if (!blk_data) + { + // Empty cell block. There is no element to release. + mdds_mtv_get_empty_value(value); + return get_iterator(block_index); + } + + assert(pos >= start_pos); + assert(blk_data); // data for non-empty blocks should never be nullptr. + size_type offset = pos - start_pos; + mdds_mtv_get_value(*blk_data, offset, value); + + // Set the element slot empty without overwriting it. + return set_empty_in_single_block(pos, pos, block_index, false); +} + +template +void multi_type_vector::swap_impl( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index1, + size_type block_index2, size_type dblock_index1, size_type dblock_index2) +{ + if (block_index1 == block_index2) + { + // Source range is in a single block. + if (dblock_index1 == dblock_index2) + { + // Destination range is also in a single block. + swap_single_block(other, start_pos, end_pos, other_pos, block_index1, dblock_index1); + } + else + { + // Source is single-, and destination is multi-blocks. + swap_single_to_multi_blocks( + other, start_pos, end_pos, other_pos, block_index1, dblock_index1, dblock_index2); + } + } + else if (dblock_index1 == dblock_index2) + { + // Destination range is over a single block. Switch source and destination. + size_type len = end_pos - start_pos + 1; + other.swap_single_to_multi_blocks( + *this, other_pos, other_pos + len - 1, start_pos, dblock_index1, block_index1, block_index2); + } + else + { + // Both source and destinations are multi-block. + swap_multi_to_multi_blocks( + other, start_pos, end_pos, other_pos, block_index1, block_index2, dblock_index1, dblock_index2); + } +} + +template +void multi_type_vector::swap_single_block( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index, + size_type other_block_index) +{ + element_block_type* src_data = m_block_store.element_blocks[block_index]; + element_block_type* dst_data = other.m_block_store.element_blocks[other_block_index]; + size_type start_pos_in_block = m_block_store.positions[block_index]; + size_type start_pos_in_other_block = other.m_block_store.positions[other_block_index]; + element_category_type cat_src = mtv::element_type_empty; + element_category_type cat_dst = mtv::element_type_empty; + + if (src_data) + cat_src = mtv::get_block_type(*src_data); + if (dst_data) + cat_dst = mtv::get_block_type(*dst_data); + + size_t other_end_pos = other_pos + end_pos - start_pos; + size_t len = end_pos - start_pos + 1; // length of elements to swap. + size_type src_offset = start_pos - start_pos_in_block; + size_type dst_offset = other_pos - start_pos_in_other_block; + + // length of the tail that will not be swapped. + size_type src_tail_len = m_block_store.sizes[block_index] - src_offset - len; + + if (cat_src == cat_dst) + { + // Source and destination blocks are of the same type. + if (cat_src == mtv::element_type_empty) + // Both are empty blocks. Nothing to swap. + return; + + block_funcs::swap_values(*src_data, *dst_data, src_offset, dst_offset, len); + return; + } + + // Source and destination blocks are of different types. + + if (cat_src == mtv::element_type_empty) + { + // Source is empty but destination is not. This is equivalent of transfer. + other.transfer_single_block(other_pos, other_end_pos, other_block_index, *this, start_pos); + // No update of local index vars needed. + return; + } + + if (cat_dst == mtv::element_type_empty) + { + // Source is not empty but destination is. Use transfer. + transfer_single_block(start_pos, end_pos, block_index, other, other_pos); + // No update of local index vars needed. + return; + } + + // Neither the source nor destination blocks are empty, and they are of different types. + if (src_offset == 0) + { + // Source range is at the top of a block. + if (src_tail_len == 0) + { + // the entire source block needs to be replaced. + std::unique_ptr src_data_original(src_data); + m_hdl_event.element_block_released(src_data); + m_block_store.element_blocks[block_index] = + other.exchange_elements(*src_data_original, src_offset, other_block_index, dst_offset, len); + src_data = m_block_store.element_blocks[block_index]; + m_hdl_event.element_block_acquired(src_data); + + // Release elements in the source block to prevent double-deletion. + block_funcs::resize_block(*src_data_original, 0); + merge_with_adjacent_blocks(block_index); + return; + } + + // Get the new elements from the other container. + std::unique_ptr new_dst_data( + other.exchange_elements(*src_data, src_offset, other_block_index, dst_offset, len)); + + // Shrink the current block by erasing the top part. + block_funcs::erase(*src_data, 0, len); + m_block_store.positions[block_index] += len; + m_block_store.sizes[block_index] -= len; + + bool blk_prev = is_previous_block_of_type(block_index, cat_dst); + if (blk_prev) + { + // Append the new elements to the previous block. + element_block_type* prev_data = m_block_store.element_blocks[block_index - 1]; + block_funcs::append_block(*prev_data, *new_dst_data); + block_funcs::resize_block(*new_dst_data, 0); // prevent double-delete. + m_block_store.sizes[block_index - 1] += len; + } + else + { + // Insert a new block to store the new elements. + size_type position = m_block_store.positions[block_index] - len; + m_block_store.insert(block_index, position, len, nullptr); + m_block_store.element_blocks[block_index] = new_dst_data.release(); + m_hdl_event.element_block_acquired(m_block_store.element_blocks[block_index]); + } + return; + } + + // Get the new elements from the destination instance. + std::unique_ptr data_from_dst( + other.exchange_elements(*src_data, src_offset, other_block_index, dst_offset, len)); + + if (src_tail_len == 0) + { + // Source range is at the bottom of a block. + + // Shrink the current block. + block_funcs::resize_block(*src_data, src_offset); + m_block_store.sizes[block_index] = src_offset; + + if (is_next_block_of_type(block_index, cat_dst)) + { + // Merge with the next block. + element_block_type* next_data = m_block_store.element_blocks[block_index + 1]; + block_funcs::prepend_values_from_block(*next_data, *data_from_dst, 0, len); + block_funcs::resize_block(*data_from_dst, 0); // prevent double-delete. + m_block_store.sizes[block_index + 1] += len; + m_block_store.positions[block_index + 1] -= len; + } + else + { + m_block_store.insert(block_index + 1, 0, len, nullptr); + m_block_store.calc_block_position(block_index + 1); + m_block_store.element_blocks[block_index + 1] = data_from_dst.release(); + m_hdl_event.element_block_acquired(m_block_store.element_blocks[block_index + 1]); + } + return; + } + + // Source range is in the middle of a block. + assert(src_offset && src_tail_len); + block_index = set_new_block_to_middle(block_index, src_offset, len, false); + m_block_store.element_blocks[block_index] = data_from_dst.release(); + m_hdl_event.element_block_acquired(m_block_store.element_blocks[block_index]); +} + +template +void multi_type_vector::swap_single_to_multi_blocks( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index, + size_type dst_block_index1, size_type dst_block_index2) +{ + element_block_type* src_data = m_block_store.element_blocks[block_index]; + size_type start_pos_in_block = m_block_store.positions[block_index]; + size_type dst_start_pos_in_block1 = other.m_block_store.positions[dst_block_index1]; + size_type dst_start_pos_in_block2 = other.m_block_store.positions[dst_block_index2]; + + element_category_type cat_src = src_data ? mtv::get_block_type(*src_data) : mtv::element_type_empty; + + size_type len = end_pos - start_pos + 1; + + if (cat_src == mtv::element_type_empty) + { + // The source block is empty. Use transfer. + other.transfer_multi_blocks( + other_pos, other_pos + len - 1, dst_block_index1, dst_block_index2, *this, start_pos); + return; + } + + size_type src_offset = start_pos - start_pos_in_block; + size_type dst_offset1 = other_pos - dst_start_pos_in_block1; + size_type dst_offset2 = other_pos + len - 1 - dst_start_pos_in_block2; + + // length of the tail that will not be swapped. + size_type src_tail_len = m_block_store.sizes[block_index] - src_offset - len; + + // Get the new elements from the other instance. + blocks_type new_blocks; + other.exchange_elements( + *src_data, src_offset, dst_block_index1, dst_offset1, dst_block_index2, dst_offset2, len, new_blocks); + + new_blocks.check_integrity(); + + if (new_blocks.positions.empty()) + throw general_error("multi_type_vector::swap_single_to_multi_blocks: failed to exchange elements."); + + if (src_offset == 0) + { + // Source range is at the top of a block. + + size_type src_position = m_block_store.positions[block_index]; + + if (src_tail_len == 0) + { + // the whole block needs to be replaced. Delete the block, but + // don't delete the managed elements the block contains since they + // have been transferred over to the destination block. + block_funcs::resize_block(*src_data, 0); + delete_element_block(block_index); + m_block_store.erase(block_index); + } + else + { + // Shrink the current block by erasing the top part. + block_funcs::erase(*src_data, 0, len); + m_block_store.sizes[block_index] -= len; + m_block_store.positions[block_index] += len; + } + + insert_blocks_at(src_position, block_index, new_blocks); + merge_with_next_block(block_index + new_blocks.positions.size() - 1); // last block inserted. + if (block_index > 0) + merge_with_next_block(block_index - 1); // block before the first block inserted. + + return; + } + + size_type position = 0; + + if (src_tail_len == 0) + { + // Source range is at the bottom of a block. + + // Shrink the current block. + block_funcs::resize_block(*src_data, src_offset); + m_block_store.sizes[block_index] = src_offset; + position = m_block_store.positions[block_index] + m_block_store.sizes[block_index]; + } + else + { + // Source range is in the middle of a block. + assert(src_offset && src_tail_len); + + // This will create two new slots at block_index+1, the first of which + // we will immediately remove. The new blocks from the other + // container will be inserted at the removed slot. + set_new_block_to_middle(block_index, src_offset, len, false); + delete_element_block(block_index + 1); + m_block_store.erase(block_index + 1); + position = m_block_store.positions[block_index] + m_block_store.sizes[block_index]; + } + + insert_blocks_at(position, block_index + 1, new_blocks); + merge_with_next_block(block_index + new_blocks.positions.size()); // last block inserted. + merge_with_next_block(block_index); // block before the first block inserted. +} + +template +void multi_type_vector::swap_multi_to_multi_blocks( + multi_type_vector& other, size_type start_pos, size_type end_pos, size_type other_pos, size_type block_index1, + size_type block_index2, size_type dblock_index1, size_type dblock_index2) +{ + assert(block_index1 < block_index2); + assert(dblock_index1 < dblock_index2); + + size_type start_pos_in_block1 = m_block_store.positions[block_index1]; + size_type start_pos_in_block2 = m_block_store.positions[block_index2]; + size_type start_pos_in_dblock1 = other.m_block_store.positions[dblock_index1]; + size_type start_pos_in_dblock2 = other.m_block_store.positions[dblock_index2]; + + size_type len = end_pos - start_pos + 1; + size_type src_offset1 = start_pos - start_pos_in_block1; + size_type src_offset2 = end_pos - start_pos_in_block2; + size_type dst_offset1 = other_pos - start_pos_in_dblock1; + size_type dst_offset2 = other_pos + len - 1 - start_pos_in_dblock2; + + blocks_to_transfer src_bucket, dst_bucket; + prepare_blocks_to_transfer(src_bucket, block_index1, src_offset1, block_index2, src_offset2); + other.prepare_blocks_to_transfer(dst_bucket, dblock_index1, dst_offset1, dblock_index2, dst_offset2); + + size_type position = 0; + if (src_bucket.insert_index > 0) + { + size_type i = src_bucket.insert_index - 1; + position = m_block_store.positions[i] + m_block_store.sizes[i]; + } + insert_blocks_at(position, src_bucket.insert_index, dst_bucket.blocks); + + // Merge the boundary blocks in the source. + merge_with_next_block(src_bucket.insert_index + dst_bucket.blocks.positions.size() - 1); + if (src_bucket.insert_index > 0) + merge_with_next_block(src_bucket.insert_index - 1); + + position = 0; + if (dst_bucket.insert_index > 0) + { + size_type i = dst_bucket.insert_index - 1; + position = other.m_block_store.positions[i] + other.m_block_store.sizes[i]; + } + other.insert_blocks_at(position, dst_bucket.insert_index, src_bucket.blocks); + + // Merge the boundary blocks in the destination. + other.merge_with_next_block(dst_bucket.insert_index + src_bucket.blocks.positions.size() - 1); + if (dst_bucket.insert_index > 0) + other.merge_with_next_block(dst_bucket.insert_index - 1); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::insert_cells_impl( + size_type row, size_type block_index, const T& it_begin, const T& it_end) +{ + size_type start_row = m_block_store.positions[block_index]; + size_type length = std::distance(it_begin, it_end); + if (!length) + // empty data array. nothing to do. + return end(); + + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + if (!blk_data) + { + if (row == start_row) + { + // Insert into an empty block. Check the previos block (if exists) + // to see if the data can be appended to it if inserting at the top + // of the block. + bool blk0 = is_previous_block_of_type(block_index, cat); + + if (blk0) + { + // Append to the previous block. + element_block_type* blk0_data = m_block_store.element_blocks[block_index - 1]; + mdds_mtv_append_values(*blk0_data, *it_begin, it_begin, it_end); + m_block_store.sizes[block_index - 1] += length; + m_cur_size += length; + adjust_block_positions_func{}(m_block_store, block_index, length); + + return get_iterator(block_index - 1); + } + + // Just insert a new block before the current block. + size_type position = m_block_store.positions[block_index]; + m_block_store.insert(block_index, position, length, nullptr); + m_block_store.element_blocks[block_index] = block_funcs::create_new_block(cat, 0); + blk_data = m_block_store.element_blocks[block_index]; + m_hdl_event.element_block_acquired(blk_data); + mdds_mtv_assign_values(*blk_data, *it_begin, it_begin, it_end); + m_cur_size += length; + adjust_block_positions_func{}(m_block_store, block_index + 1, length); + + return get_iterator(block_index); + } + + insert_cells_to_middle(row, block_index, it_begin, it_end); + m_cur_size += length; + + return get_iterator(block_index + 1); + } + + assert(blk_data); + element_category_type blk_cat = mdds::mtv::get_block_type(*blk_data); + if (cat == blk_cat) + { + // Simply insert the new data series into existing block. + assert(it_begin != it_end); + mdds_mtv_insert_values(*blk_data, row - start_row, *it_begin, it_begin, it_end); + m_block_store.sizes[block_index] += length; + m_cur_size += length; + adjust_block_positions_func{}(m_block_store, block_index + 1, length); + + return get_iterator(block_index); + } + + assert(cat != blk_cat); + if (row == start_row) + { + // Check the previous block to see if we can append the data there. + bool blk0 = is_previous_block_of_type(block_index, cat); + if (blk0) + { + // Append to the previous block. + element_block_type* blk0_data = m_block_store.element_blocks[block_index - 1]; + mdds_mtv_append_values(*blk0_data, *it_begin, it_begin, it_end); + m_block_store.sizes[block_index - 1] += length; + m_cur_size += length; + adjust_block_positions_func{}(m_block_store, block_index, length); + + return get_iterator(block_index - 1); + } + + // Just insert a new block before the current block. + m_block_store.insert(block_index, m_block_store.positions[block_index], length, nullptr); + m_block_store.element_blocks[block_index] = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(blk_data); + blk_data = m_block_store.element_blocks[block_index]; + mdds_mtv_assign_values(*blk_data, *it_begin, it_begin, it_end); + m_block_store.sizes[block_index] = length; + m_cur_size += length; + adjust_block_positions_func{}(m_block_store, block_index + 1, length); + + return get_iterator(block_index); + } + + insert_cells_to_middle(row, block_index, it_begin, it_end); + m_cur_size += length; + + return get_iterator(block_index + 1); +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty_impl( + size_type start_pos, size_type end_pos, size_type block_index1, bool overwrite) +{ + if (start_pos > end_pos) + throw std::out_of_range("Start row is larger than the end row."); + + size_type block_index2 = get_block_position(end_pos, block_index1); + if (block_index2 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set_empty_impl", __LINE__, end_pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + iterator ret_it; + if (block_index1 == block_index2) + ret_it = set_empty_in_single_block(start_pos, end_pos, block_index1, overwrite); + else + ret_it = set_empty_in_multi_blocks(start_pos, end_pos, block_index1, block_index2, overwrite); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in set_empty(start_pos=" << start_pos << "; end_pos=" << end_pos << ")" + << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + return ret_it; +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty_in_single_block( + size_type start_row, size_type end_row, size_type block_index, bool overwrite) +{ + // Range is within a single block. + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + if (!blk_data) + // This block is already empty. Do nothing. + return get_iterator(block_index); + + size_type start_row_in_block = m_block_store.positions[block_index]; + assert(start_row_in_block + m_block_store.sizes[block_index] >= 1); + size_type end_row_in_block = start_row_in_block + m_block_store.sizes[block_index] - 1; + size_type empty_block_size = end_row - start_row + 1; + + if (start_row == start_row_in_block) + { + // start row coincides with the start of a block. + + if (end_row == end_row_in_block) + return set_whole_block_empty(block_index, overwrite); + + // Set the upper part of the block empty. + if (overwrite) + block_funcs::overwrite_values(*blk_data, 0, empty_block_size); + + block_funcs::erase(*blk_data, 0, empty_block_size); + m_block_store.sizes[block_index] -= empty_block_size; + + // Check if the preceding block (if exists) is also empty. + bool blk_prev = is_previous_block_of_type(block_index, mtv::element_type_empty); + if (blk_prev) + { + // Extend the previous empty block. + m_block_store.sizes[block_index - 1] += empty_block_size; + m_block_store.positions[block_index] += empty_block_size; + return get_iterator(block_index - 1); + } + + // Insert a new empty block before the current one. + size_type block_position = m_block_store.positions[block_index]; + m_block_store.positions[block_index] += empty_block_size; + m_block_store.insert(block_index, block_position, empty_block_size, nullptr); + return get_iterator(block_index); + } + + if (end_row == end_row_in_block) + { + // end row equals the end of a block. + assert(start_row > start_row_in_block); + + // Set the lower part of the block empty. + size_type start_pos = start_row - start_row_in_block; + if (overwrite) + block_funcs::overwrite_values(*blk_data, start_pos, empty_block_size); + + block_funcs::erase(*blk_data, start_pos, empty_block_size); + m_block_store.sizes[block_index] -= empty_block_size; + + // Check if the following block (if exists) is also empty. + bool blk_next = is_next_block_of_type(block_index, mtv::element_type_empty); + if (blk_next) + { + // Extend the next empty block to cover the new empty segment. + m_block_store.sizes[block_index + 1] += empty_block_size; + m_block_store.positions[block_index + 1] = start_row; + } + else + { + // Insert a new empty block after the current one. + m_block_store.insert(block_index + 1, start_row, empty_block_size, nullptr); + } + + return get_iterator(block_index + 1); + } + + // Empty the middle part of a block. + assert(end_row_in_block - end_row > 0); + set_new_block_to_middle(block_index, start_row - start_row_in_block, empty_block_size, overwrite); + + return get_iterator(block_index + 1); +} + +template +typename multi_type_vector::iterator multi_type_vector::set_empty_in_multi_blocks( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, bool overwrite) +{ + assert(block_index1 < block_index2); + size_type start_row_in_block1 = m_block_store.positions[block_index1]; + size_type start_row_in_block2 = m_block_store.positions[block_index2]; + + { + // Empty the lower part of the first block. + element_block_type* blk_data = m_block_store.element_blocks[block_index1]; + if (blk_data) + { + if (start_row_in_block1 == start_row) + { + // Empty the whole block. + + bool prev_empty = is_previous_block_of_type(block_index1, mtv::element_type_empty); + if (prev_empty) + { + // Previous block is empty. Move the start row to the + // first row of the previous block, and make the previous + // block 'block 1'. + start_row -= m_block_store.sizes[block_index1 - 1]; + --block_index1; + } + else + { + // Make block 1 empty. + if (!overwrite) + block_funcs::resize_block(*blk_data, 0); + + delete_element_block(block_index1); + } + } + else + { + // Empty the lower part. + size_type new_size = start_row - start_row_in_block1; + if (overwrite) + block_funcs::overwrite_values(*blk_data, new_size, m_block_store.sizes[block_index1] - new_size); + + block_funcs::resize_block(*blk_data, new_size); + m_block_store.sizes[block_index1] = new_size; + } + } + else + { + // First block is already empty. Adjust the start row of the new + // empty range. + start_row = start_row_in_block1; + } + } + + size_type end_block_to_erase = block_index2; // End block position is non-inclusive. + + { + // Empty the upper part of the last block. + element_block_type* blk_data = m_block_store.element_blocks[block_index2]; + size_type last_row_in_block = start_row_in_block2 + m_block_store.sizes[block_index2] - 1; + + if (blk_data) + { + if (last_row_in_block == end_row) + { + // Delete the whole block. + ++end_block_to_erase; + + // Check if the following block (if exists) is also empty. + bool next_empty = is_next_block_of_type(block_index2, mtv::element_type_empty); + if (next_empty) + { + // The following block is also empty. + end_row += m_block_store.sizes[block_index2 + 1]; + ++end_block_to_erase; + } + } + else + { + // Empty the upper part. + size_type size_to_erase = end_row - start_row_in_block2 + 1; + if (overwrite) + block_funcs::overwrite_values(*blk_data, 0, size_to_erase); + + block_funcs::erase(*blk_data, 0, size_to_erase); + m_block_store.sizes[block_index2] -= size_to_erase; + m_block_store.positions[block_index2] = start_row_in_block2 + size_to_erase; + } + } + else + { + // Last block is empty. Delete this block and adjust the end row + // of the new empty range. + ++end_block_to_erase; + end_row = last_row_in_block; + } + } + + if (end_block_to_erase - block_index1 > 1) + { + // Remove all blocks in-between, from block_index1+1 to end_block_to_erase-1. + + for (size_type i = block_index1 + 1; i < end_block_to_erase; ++i) + { + element_block_type* data = m_block_store.element_blocks[i]; + if (!overwrite && data) + block_funcs::resize_block(*data, 0); + + delete_element_block(i); + } + + size_type n_erase_blocks = end_block_to_erase - block_index1 - 1; + m_block_store.erase(block_index1 + 1, n_erase_blocks); + } + + element_block_type* blk_data = m_block_store.element_blocks[block_index1]; + size_type empty_block_size = end_row - start_row + 1; + if (blk_data) + { + // Insert a new empty block after the first block. + m_block_store.insert(block_index1 + 1, start_row, empty_block_size, nullptr); + return get_iterator(block_index1 + 1); + } + + // Current block is already empty. Just extend its size. + m_block_store.sizes[block_index1] = empty_block_size; + m_block_store.positions[block_index1] = start_row; + return get_iterator(block_index1); +} + +template +void multi_type_vector::erase_impl(size_type start_row, size_type end_row) +{ + assert(start_row <= end_row); + + // Keep the logic similar to set_empty(). + + size_type block_pos1 = get_block_position(start_row); + if (block_pos1 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::erase_impl", __LINE__, start_row, block_size(), size()); + + size_type block_pos2 = get_block_position(end_row, block_pos1); + if (block_pos2 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::erase_impl", __LINE__, start_row, block_size(), size()); + + size_type start_row_in_block1 = m_block_store.positions[block_pos1]; + size_type start_row_in_block2 = m_block_store.positions[block_pos2]; + + if (block_pos1 == block_pos2) + { + erase_in_single_block(start_row, end_row, block_pos1); + return; + } + + assert(block_pos1 < block_pos2); + + // Initially, we set to erase all blocks between the first and the last. + size_type index_erase_begin = block_pos1 + 1; + size_type index_erase_end = block_pos2; + + // First, inspect the first block. + if (start_row_in_block1 == start_row) + { + // Erase the whole block. + --index_erase_begin; + } + else + { + // Erase the lower part of the first element block. + element_block_type* blk_data = m_block_store.element_blocks[block_pos1]; + size_type new_size = start_row - start_row_in_block1; + if (blk_data) + { + // Shrink the element block. + block_funcs::overwrite_values(*blk_data, new_size, m_block_store.sizes[block_pos1] - new_size); + block_funcs::resize_block(*blk_data, new_size); + } + m_block_store.sizes[block_pos1] = new_size; + } + + size_type adjust_block_offset = 0; + + // Then inspect the last block. + size_type last_row_in_block = start_row_in_block2 + m_block_store.sizes[block_pos2] - 1; + if (last_row_in_block == end_row) + { + // Delete the whole block. + ++index_erase_end; + } + else + { + size_type size_to_erase = end_row - start_row_in_block2 + 1; + m_block_store.sizes[block_pos2] -= size_to_erase; + m_block_store.positions[block_pos2] = start_row; + element_block_type* blk_data = m_block_store.element_blocks[block_pos2]; + if (blk_data) + { + // Erase the upper part. + block_funcs::overwrite_values(*blk_data, 0, size_to_erase); + block_funcs::erase(*blk_data, 0, size_to_erase); + } + + adjust_block_offset = 1; // Exclude this block from later block position adjustment. + } + + // Get the index of the block that sits before the blocks being erased. + block_pos1 = index_erase_begin; + if (block_pos1 > 0) + --block_pos1; + + // Now, erase all blocks in between. + delete_element_blocks(index_erase_begin, index_erase_end); + m_block_store.erase(index_erase_begin, index_erase_end - index_erase_begin); + int64_t delta = end_row - start_row + 1; + m_cur_size -= delta; + + if (m_block_store.positions.empty()) + return; + + // Adjust the positions of the blocks following the erased. + size_type adjust_pos = index_erase_begin; + adjust_pos += adjust_block_offset; + adjust_block_positions_func{}(m_block_store, adjust_pos, -delta); + merge_with_next_block(block_pos1); +} + +template +void multi_type_vector::erase_in_single_block(size_type start_pos, size_type end_pos, size_type block_index) +{ + // Range falls within the same block. + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + int64_t size_to_erase = end_pos - start_pos + 1; + + if (blk_data) + { + // Erase data in the data block. + size_type offset = start_pos - m_block_store.positions[block_index]; + block_funcs::overwrite_values(*blk_data, offset, size_to_erase); + block_funcs::erase(*blk_data, offset, size_to_erase); + } + + m_block_store.sizes[block_index] -= size_to_erase; + m_cur_size -= size_to_erase; + + if (m_block_store.sizes[block_index]) + { + // Block still contains data. Bail out. + adjust_block_positions_func{}(m_block_store, block_index + 1, -size_to_erase); + return; + } + + // Delete the current block since it has become empty. + delete_element_block(block_index); + m_block_store.erase(block_index); + + if (block_index == 0) + { + // Deleted block was the first block. + adjust_block_positions_func{}(m_block_store, block_index, -size_to_erase); + return; + } + + if (block_index >= m_block_store.positions.size()) + // Deleted block was the last block. + return; + + // Check the previous and next blocks to see if they should be merged. + element_block_type* prev_data = m_block_store.element_blocks[block_index - 1]; + element_block_type* next_data = m_block_store.element_blocks[block_index]; + + if (prev_data) + { + // Previous block has data. + if (!next_data) + { + // Next block is empty. Nothing to do. + adjust_block_positions_func{}(m_block_store, block_index, -size_to_erase); + return; + } + + element_category_type cat1 = mdds::mtv::get_block_type(*prev_data); + element_category_type cat2 = mdds::mtv::get_block_type(*next_data); + + if (cat1 == cat2) + { + // Merge the two blocks. + block_funcs::append_block(*prev_data, *next_data); + m_block_store.sizes[block_index - 1] += m_block_store.sizes[block_index]; + // Resize to 0 to prevent deletion of cells in case of managed cells. + block_funcs::resize_block(*next_data, 0); + delete_element_block(block_index); + m_block_store.erase(block_index); + } + + adjust_block_positions_func{}(m_block_store, block_index, -size_to_erase); + } + else + { + // Previous block is empty. + if (next_data) + { + // Next block is not empty. Nothing to do. + adjust_block_positions_func{}(m_block_store, block_index, -size_to_erase); + return; + } + + // Both blocks are empty. Simply increase the size of the previous + // block. + m_block_store.sizes[block_index - 1] += m_block_store.sizes[block_index]; + delete_element_block(block_index); + m_block_store.erase(block_index); + adjust_block_positions_func{}(m_block_store, block_index, -size_to_erase); + } +} + +template +typename multi_type_vector::iterator multi_type_vector::insert_empty_impl( + size_type pos, size_type block_index, size_type length) +{ + assert(pos < m_cur_size); + + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + + if (!blk_data) + { + // Insertion point is already empty. Just expand its size and be done + // with it. + m_block_store.sizes[block_index] += length; + m_cur_size += length; + adjust_block_positions_func{}(m_block_store, block_index + 1, length); + return get_iterator(block_index); + } + + size_type start_pos = m_block_store.positions[block_index]; + + if (start_pos == pos) + { + // Insertion point is at the top of an existing non-empty block. + bool blk_prev = is_previous_block_of_type(block_index, mtv::element_type_empty); + if (blk_prev) + { + // Previous block is empty. Expand the size of the previous block. + assert(!m_block_store.element_blocks[block_index - 1]); + m_block_store.sizes[block_index - 1] += length; + m_cur_size += length; + adjust_block_positions_func{}(m_block_store, block_index, length); + return get_iterator(block_index - 1); + } + + // Insert a new empty block. + m_block_store.insert(block_index, start_pos, length, nullptr); + m_cur_size += length; + adjust_block_positions_func{}(m_block_store, block_index + 1, length); + return get_iterator(block_index); + } + + assert(blk_data); + assert(pos > start_pos); + + size_type size_blk_prev = pos - start_pos; + size_type size_blk_next = m_block_store.sizes[block_index] - size_blk_prev; + + // Insert two new blocks below the current; one for the empty block being + // inserted, and the other for the lower part of the current non-empty + // block. + m_block_store.insert(block_index + 1, 2u); + + m_block_store.sizes[block_index + 1] = length; + m_block_store.sizes[block_index + 2] = size_blk_next; + + m_block_store.element_blocks[block_index + 2] = + block_funcs::create_new_block(mdds::mtv::get_block_type(*blk_data), 0); + element_block_type* next_data = m_block_store.element_blocks[block_index + 2]; + m_hdl_event.element_block_acquired(next_data); + + // Check if the previous block is the bigger one + if (size_blk_prev > size_blk_next) + { + // Upper (previous) block is larger than the lower (next) block. Copy + // the lower values to the next block. + block_funcs::assign_values_from_block(*next_data, *blk_data, size_blk_prev, size_blk_next); + block_funcs::resize_block(*blk_data, size_blk_prev); + m_block_store.sizes[block_index] = size_blk_prev; + } + else + { + // Lower (next) block is larger than the upper (previous) block. Copy + // the upper values to the "next" block. + block_funcs::assign_values_from_block(*next_data, *blk_data, 0, size_blk_prev); + m_block_store.sizes[block_index + 2] = size_blk_prev; + + // Remove the copied values and push the rest to the top. + block_funcs::erase(*blk_data, 0, size_blk_prev); + + // Set the size of the current block to its new size ( what is after the new block ) + m_block_store.sizes[block_index] = size_blk_next; + + // And now let's swap the blocks, but save the block position. + size_type position = m_block_store.positions[block_index]; + m_block_store.swap(block_index, block_index + 2); + m_block_store.positions[block_index] = position; + } + + m_cur_size += length; + m_block_store.calc_block_position(block_index + 1); + m_block_store.calc_block_position(block_index + 2); + adjust_block_positions_func{}(m_block_store, block_index + 3, length); + + return get_iterator(block_index + 1); +} + +template +void multi_type_vector::insert_blocks_at(size_type position, size_type insert_pos, blocks_type& new_blocks) +{ + for (size_type i = 0; i < new_blocks.positions.size(); ++i) + { + new_blocks.positions[i] = position; + position += new_blocks.sizes[i]; + + const element_block_type* data = new_blocks.element_blocks[i]; + + if (data) + m_hdl_event.element_block_acquired(data); + } + + m_block_store.insert(insert_pos, new_blocks); +} + +template +void multi_type_vector::prepare_blocks_to_transfer( + blocks_to_transfer& bucket, size_type block_index1, size_type offset1, size_type block_index2, size_type offset2) +{ + assert(block_index1 < block_index2); + assert(offset1 < m_block_store.sizes[block_index1]); + assert(offset2 < m_block_store.sizes[block_index2]); + + block_slot_type block_first; + block_slot_type block_last; + size_type index_begin = block_index1 + 1; + size_type index_end = block_index2; + + bucket.insert_index = block_index1 + 1; + + if (offset1 == 0) + { + // The whole first block needs to be swapped. + --index_begin; + --bucket.insert_index; + } + else + { + // Copy the lower part of the block for transfer. + size_type blk_size = m_block_store.sizes[block_index1] - offset1; + block_first.size = blk_size; + + element_block_type* blk_data1 = m_block_store.element_blocks[block_index1]; + if (blk_data1) + { + block_first.element_block = block_funcs::create_new_block(mtv::get_block_type(*blk_data1), 0); + block_funcs::assign_values_from_block(*block_first.element_block, *blk_data1, offset1, blk_size); + + // Shrink the existing block. + block_funcs::resize_block(*blk_data1, offset1); + } + + m_block_store.sizes[block_index1] = offset1; + } + + if (offset2 == m_block_store.sizes[block_index2] - 1) + { + // The entire last block needs to be swapped. + ++index_end; + } + else + { + // Copy the upper part of the last block for transfer. + size_type blk_size = offset2 + 1; + block_last.size = blk_size; + element_block_type* blk_data2 = m_block_store.element_blocks[block_index2]; + + if (blk_data2) + { + block_last.element_block = block_funcs::create_new_block(mtv::get_block_type(*blk_data2), 0); + block_funcs::assign_values_from_block(*block_last.element_block, *blk_data2, 0, blk_size); + + // Shrink the existing block. + block_funcs::erase(*blk_data2, 0, blk_size); + } + + m_block_store.positions[block_index2] += blk_size; + m_block_store.sizes[block_index2] -= blk_size; + } + + // Copy all blocks into the bucket. + if (block_first.size) + bucket.blocks.push_back(block_first); + + for (size_type i = index_begin; i < index_end; ++i) + { + element_block_type* data = m_block_store.element_blocks[i]; + if (data) + m_hdl_event.element_block_released(data); + + bucket.blocks.push_back(m_block_store.positions[i], m_block_store.sizes[i], m_block_store.element_blocks[i]); + } + + if (block_last.size) + bucket.blocks.push_back(block_last); + + // Remove the slots for these blocks (but don't delete the blocks). + m_block_store.erase(index_begin, index_end - index_begin); +} + +template +typename multi_type_vector::iterator multi_type_vector::set_whole_block_empty( + size_type block_index, bool overwrite) +{ + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + if (!overwrite) + // Resize block to 0 before deleting, to prevent its elements from getting deleted. + block_funcs::resize_block(*blk_data, 0); + + delete_element_block(block_index); + + bool blk_prev = is_previous_block_of_type(block_index, mtv::element_type_empty); + bool blk_next = is_next_block_of_type(block_index, mtv::element_type_empty); + + // Merge with adjacent block(s) if necessary. + if (blk_prev) + { + assert(!m_block_store.element_blocks[block_index - 1]); + + if (blk_next) + { + // Both preceding and next blocks are empty. + assert(!m_block_store.element_blocks[block_index + 1]); + + m_block_store.sizes[block_index - 1] += + m_block_store.sizes[block_index] + m_block_store.sizes[block_index + 1]; + + // No need delete the current and next element blocks since they are both empty. + m_block_store.erase(block_index, 2); + + return get_iterator(block_index - 1); + } + + // Only the preceding block is empty. Merge the current block with the previous. + m_block_store.sizes[block_index - 1] += m_block_store.sizes[block_index]; + m_block_store.erase(block_index); + + return get_iterator(block_index - 1); + } + else if (blk_next) + { + assert(!m_block_store.element_blocks[block_index + 1]); + + // Only the next block is empty. Merge the next block with the current. + m_block_store.sizes[block_index] += m_block_store.sizes[block_index + 1]; + m_block_store.erase(block_index + 1); + + return get_iterator(block_index); + } + + return get_iterator(block_index); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_impl( + size_type row, size_type end_row, size_type block_index1, const T& it_begin, const T& it_end) +{ + size_type block_index2 = get_block_position(end_row, block_index1); + if (block_index2 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::set_cells_impl", __LINE__, end_row, block_size(), size()); + + if (block_index1 == block_index2) + { + // The whole data array will fit in a single block. + return set_cells_to_single_block(row, end_row, block_index1, it_begin, it_end); + } + + return set_cells_to_multi_blocks(row, end_row, block_index1, block_index2, it_begin, it_end); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_to_single_block( + size_type start_row, size_type end_row, size_type block_index, const T& it_begin, const T& it_end) +{ + assert(it_begin != it_end); + assert(!m_block_store.positions.empty()); + + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + size_type start_row_in_block = m_block_store.positions[block_index]; + size_type data_length = std::distance(it_begin, it_end); + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + + if (blk_data && mdds::mtv::get_block_type(*blk_data) == cat) + { + // t|---|xxx|---|b - Simple overwrite. + size_type offset = start_row - start_row_in_block; + block_funcs::overwrite_values(*blk_data, offset, data_length); + if (!offset && data_length == m_block_store.sizes[block_index]) + // Overwrite the whole block. It's faster to use assign_values. + mdds_mtv_assign_values(*blk_data, *it_begin, it_begin, it_end); + else + mdds_mtv_set_values(*blk_data, offset, *it_begin, it_begin, it_end); + + return get_iterator(block_index); + } + + size_type end_row_in_block = start_row_in_block + m_block_store.sizes[block_index] - 1; + if (start_row == start_row_in_block) + { + if (end_row == end_row_in_block) + { + // t|???|xxx|???|b - Replace the entire current block, but first try + // to see if the values can be appended to the previous block. + if (append_to_prev_block(block_index, cat, end_row - start_row + 1, it_begin, it_end)) + { + delete_element_block(block_index); + m_block_store.erase(block_index); + + // Check if we need to merge it with the next block. + --block_index; + merge_with_next_block(block_index); + + return get_iterator(block_index); + } + + // Replace the whole block. + if (blk_data) + { + m_hdl_event.element_block_released(blk_data); + block_funcs::delete_block(blk_data); + } + + m_block_store.element_blocks[block_index] = block_funcs::create_new_block(cat, 0); + blk_data = m_block_store.element_blocks[block_index]; + m_hdl_event.element_block_acquired(blk_data); + mdds_mtv_assign_values(*blk_data, *it_begin, it_begin, it_end); + merge_with_next_block(block_index); + return get_iterator(block_index); + } + + // t|???|xxx--|???|b - Replace the upper part of the block. Shrink the + // current block first. + size_type length = end_row_in_block - end_row; + m_block_store.sizes[block_index] = length; + + if (blk_data) + { + // Erase the upper part of the data from the current element block. + std::unique_ptr new_data( + block_funcs::create_new_block(mdds::mtv::get_block_type(*blk_data), 0)); + + if (!new_data) + throw std::logic_error("failed to create a new element block."); + + size_type pos = end_row - start_row_in_block + 1; + block_funcs::assign_values_from_block(*new_data, *blk_data, pos, length); + block_funcs::overwrite_values(*blk_data, 0, pos); + + block_funcs::resize_block(*blk_data, 0); // to prevent deletion of elements + block_funcs::delete_block(blk_data); + m_block_store.element_blocks[block_index] = new_data.release(); + + // We intentionally don't trigger element block events here. + } + + length = end_row - start_row + 1; + if (append_to_prev_block(block_index, cat, length, it_begin, it_end)) + { + // The new values have been successfully appended to the previous block. + m_block_store.positions[block_index] += length; + return get_iterator(block_index - 1); + } + + // Insert a new block before the current block, and populate it with + // the new data. + size_type position = m_block_store.positions[block_index]; + m_block_store.positions[block_index] += length; + m_block_store.insert(block_index, position, length, nullptr); + m_block_store.element_blocks[block_index] = block_funcs::create_new_block(cat, 0); + blk_data = m_block_store.element_blocks[block_index]; + m_hdl_event.element_block_acquired(blk_data); + m_block_store.sizes[block_index] = length; + mdds_mtv_assign_values(*blk_data, *it_begin, it_begin, it_end); + + return get_iterator(block_index); + } + + assert(start_row > start_row_in_block); + if (end_row == end_row_in_block) + { + // t|???|--xxx|???|b - Shrink the end of the current block and insert a + // new block for the new data series after the current block. + size_type new_size = start_row - start_row_in_block; + m_block_store.sizes[block_index] = new_size; + + if (blk_data) + { + block_funcs::overwrite_values(*blk_data, new_size, data_length); + block_funcs::resize_block(*blk_data, new_size); + } + + new_size = end_row - start_row + 1; // size of the data array being inserted. + + if (block_index < m_block_store.positions.size() - 1) + { + // t|???|--xxx|???|b - There is a block after the current block.. + // There is a block (or more) after the current block. Check the next block. + bool blk_next = is_next_block_of_type(block_index, cat); + if (blk_next) + { + // t|???|--xxx|xxx|b - Prepend it to the next block. + mdds_mtv_prepend_values(*m_block_store.element_blocks[block_index + 1], *it_begin, it_begin, it_end); + m_block_store.sizes[block_index + 1] += new_size; + m_block_store.positions[block_index + 1] -= new_size; + return get_iterator(block_index + 1); + } + + // t|???|--xxx|---|b - Next block has a different data type. Do the + // normal insertion. + m_block_store.insert(block_index + 1, 0, new_size, nullptr); + m_block_store.calc_block_position(block_index + 1); + m_block_store.element_blocks[block_index + 1] = block_funcs::create_new_block(cat, 0); + blk_data = m_block_store.element_blocks[block_index + 1]; + m_hdl_event.element_block_acquired(blk_data); + mdds_mtv_assign_values(*blk_data, *it_begin, it_begin, it_end); + + return get_iterator(block_index + 1); + } + + // t|???|--xxx|b - Last block. + assert(block_index == m_block_store.positions.size() - 1); + + m_block_store.push_back(m_cur_size - new_size, new_size, nullptr); + m_block_store.element_blocks.back() = block_funcs::create_new_block(cat, 0); + blk_data = m_block_store.element_blocks.back(); + m_hdl_event.element_block_acquired(blk_data); + mdds_mtv_assign_values(*blk_data, *it_begin, it_begin, it_end); + + return get_iterator(block_index + 1); + } + + // t|???|-xx-|???|b - New values will be in the middle of the current block. + assert(start_row_in_block < start_row && end_row < end_row_in_block); + + block_index = set_new_block_to_middle(block_index, start_row - start_row_in_block, end_row - start_row + 1, true); + + m_block_store.element_blocks[block_index] = block_funcs::create_new_block(cat, 0); + blk_data = m_block_store.element_blocks[block_index]; + m_hdl_event.element_block_acquired(blk_data); + mdds_mtv_assign_values(*blk_data, *it_begin, it_begin, it_end); + + return get_iterator(block_index); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_to_multi_blocks( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end) +{ + assert(block_index1 < block_index2); + assert(it_begin != it_end); + assert(!m_block_store.positions.empty()); + + element_block_type* blk1_data = m_block_store.element_blocks[block_index1]; + if (blk1_data) + { + return set_cells_to_multi_blocks_block1_non_empty( + start_row, end_row, block_index1, block_index2, it_begin, it_end); + } + + // Block 1 is empty. + assert(!blk1_data); + + return set_cells_to_multi_blocks_block1_non_equal(start_row, end_row, block_index1, block_index2, it_begin, it_end); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_to_multi_blocks_block1_non_equal( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end) +{ + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + element_block_type* blk1_data = m_block_store.element_blocks[block_index1]; + element_block_type* blk2_data = m_block_store.element_blocks[block_index2]; + + size_type start_row_in_block1 = m_block_store.positions[block_index1]; + size_type start_row_in_block2 = m_block_store.positions[block_index2]; + size_type length = std::distance(it_begin, it_end); + size_type offset = start_row - start_row_in_block1; + size_type end_row_in_block2 = start_row_in_block2 + m_block_store.sizes[block_index2] - 1; + + // Initially set to erase blocks between block 1 and block 2 non-inclusive at either end. + size_type index_erase_begin = block_index1 + 1; + size_type index_erase_end = block_index2; + + // Create the new data block first. + block_slot_type data_blk(start_row, length); + + bool blk0_copied = false; + if (offset == 0) + { + // Remove block 1. + --index_erase_begin; + + // Check the type of the previous block (block 0) if exists. + if (block_index1 > 0) + { + element_block_type* blk0_data = m_block_store.element_blocks[block_index1 - 1]; + if (blk0_data && cat == mdds::mtv::get_block_type(*blk0_data)) + { + // Transfer the whole data from block 0 to data block. + data_blk.element_block = blk0_data; + m_block_store.element_blocks[block_index1 - 1] = nullptr; + + data_blk.size += m_block_store.sizes[block_index1 - 1]; + data_blk.position = m_block_store.positions[block_index1 - 1]; + + --index_erase_begin; + blk0_copied = true; + } + } + } + else + { + // Shrink block 1 by the end. + if (blk1_data) + { + size_type n = m_block_store.sizes[block_index1] - offset; + block_funcs::overwrite_values(*blk1_data, offset, n); + block_funcs::resize_block(*blk1_data, offset); + } + m_block_store.sizes[block_index1] = offset; + } + + if (blk0_copied) + { + mdds_mtv_append_values(*data_blk.element_block, *it_begin, it_begin, it_end); + } + else + { + data_blk.element_block = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(data_blk.element_block); + mdds_mtv_assign_values(*data_blk.element_block, *it_begin, it_begin, it_end); + } + + if (end_row == end_row_in_block2) + { + // Remove block 2. + ++index_erase_end; + + if (block_index2 + 1 < m_block_store.positions.size()) + { + // There is at least one block after block 2. + element_block_type* blk3_data = m_block_store.element_blocks[block_index2 + 1]; + if (blk3_data && mdds::mtv::get_block_type(*blk3_data) == cat) + { + // Merge the whole block 3 with the new data. Remove block 3 + // afterward. Resize block 3 to zero to prevent invalid free. + block_funcs::append_block(*data_blk.element_block, *blk3_data); + block_funcs::resize_block(*blk3_data, 0); + data_blk.size += m_block_store.sizes[block_index2 + 1]; + ++index_erase_end; + } + } + } + else + { + bool erase_upper = true; + + if (blk2_data) + { + element_category_type blk_cat2 = mdds::mtv::get_block_type(*blk2_data); + if (blk_cat2 == cat) + { + // Merge the lower part of block 2 with the new data, and erase + // block 2. Resize block 2 to avoid invalid free on the copied + // portion of the block. + size_type copy_pos = end_row - start_row_in_block2 + 1; + size_type size_to_copy = end_row_in_block2 - end_row; + block_funcs::append_values_from_block(*data_blk.element_block, *blk2_data, copy_pos, size_to_copy); + block_funcs::resize_block(*blk2_data, copy_pos); + data_blk.size += size_to_copy; + + ++index_erase_end; + erase_upper = false; + } + } + + if (erase_upper) + { + // Erase the upper part of block 2. + size_type size_to_erase = end_row - start_row_in_block2 + 1; + + if (blk2_data) + { + block_funcs::overwrite_values(*blk2_data, 0, size_to_erase); + block_funcs::erase(*blk2_data, 0, size_to_erase); + } + + m_block_store.sizes[block_index2] -= size_to_erase; + m_block_store.positions[block_index2] += size_to_erase; + } + } + + size_type insert_pos = index_erase_begin; + + // Remove the in-between blocks first. + delete_element_blocks(index_erase_begin, index_erase_end); + m_block_store.erase(index_erase_begin, index_erase_end - index_erase_begin); + + // Insert the new data block. + + m_block_store.insert(insert_pos, data_blk.position, data_blk.size, data_blk.element_block); + return get_iterator(insert_pos); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cells_to_multi_blocks_block1_non_empty( + size_type start_row, size_type end_row, size_type block_index1, size_type block_index2, const T& it_begin, + const T& it_end) +{ + size_type start_row_in_block1 = m_block_store.positions[block_index1]; + size_type start_row_in_block2 = m_block_store.positions[block_index2]; + + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + element_block_type* blk1_data = m_block_store.element_blocks[block_index1]; + assert(blk1_data); + element_category_type blk_cat1 = mdds::mtv::get_block_type(*blk1_data); + + if (blk_cat1 == cat) + { + size_type length = std::distance(it_begin, it_end); + size_type offset = start_row - start_row_in_block1; + size_type end_row_in_block2 = start_row_in_block2 + m_block_store.sizes[block_index2] - 1; + + // Initially set to erase blocks between block 1 and block 2 non-inclusive at either end. + size_type index_erase_begin = block_index1 + 1; + size_type index_erase_end = block_index2; + + // Extend the first block to store the new data set. + + // Shrink it first to remove the old values, then append new values. + block_funcs::overwrite_values(*blk1_data, offset, m_block_store.sizes[block_index1] - offset); + block_funcs::resize_block(*blk1_data, offset); + mdds_mtv_append_values(*blk1_data, *it_begin, it_begin, it_end); + m_block_store.sizes[block_index1] = offset + length; + + element_block_type* blk2_data = m_block_store.element_blocks[block_index2]; + + if (end_row == end_row_in_block2) + { + // Data overlaps the entire block 2. Erase it. + ++index_erase_end; + } + else if (blk2_data) + { + element_category_type blk_cat2 = mdds::mtv::get_block_type(*blk2_data); + + if (blk_cat2 == cat) + { + // Copy the lower (non-overwritten) part of block 2 to block 1, + // and remove the whole block 2. Resize block 2 to zero first to + // prevent the transferred / overwritten cells from being + // deleted on block deletion. + size_type data_length = end_row_in_block2 - end_row; + size_type begin_pos = end_row - start_row_in_block2 + 1; + block_funcs::append_values_from_block(*blk1_data, *blk2_data, begin_pos, data_length); + block_funcs::overwrite_values(*blk2_data, 0, begin_pos); + block_funcs::resize_block(*blk2_data, 0); + m_block_store.sizes[block_index1] += data_length; + ++index_erase_end; + } + else + { + // Erase the upper part of block 2. + size_type size_to_erase = end_row - start_row_in_block2 + 1; + block_funcs::erase(*blk2_data, 0, size_to_erase); + m_block_store.sizes[block_index2] -= size_to_erase; + m_block_store.positions[block_index2] += size_to_erase; + } + } + else + { + // Last block is empty. + size_type size_to_erase = end_row - start_row_in_block2 + 1; + m_block_store.sizes[block_index2] -= size_to_erase; + m_block_store.positions[block_index2] += size_to_erase; + } + + delete_element_blocks(index_erase_begin, index_erase_end); + m_block_store.erase(index_erase_begin, index_erase_end - index_erase_begin); + + return get_iterator(block_index1); + } + + // The first block type is different. + assert(blk_cat1 != cat); + + return set_cells_to_multi_blocks_block1_non_equal(start_row, end_row, block_index1, block_index2, it_begin, it_end); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cell_to_empty_block( + size_type block_index, size_type pos_in_block, const T& cell) +{ + assert(!m_block_store.element_blocks[block_index]); // In this call, the current block is an empty block. + + if (block_index == 0) + { + // Topmost block. + if (m_block_store.positions.size() == 1) + { + // this is the only block. + assert(m_block_store.sizes[block_index] == m_cur_size); + if (m_cur_size == 1) + { + // This column is allowed to have only one row! + assert(pos_in_block == 0); + create_new_block_with_new_cell(block_index, cell); + return begin(); + } + + // block has multiple rows. + if (pos_in_block == 0) + { + // Insert into the first cell in this block. + m_block_store.sizes[block_index] -= 1; + assert(m_block_store.sizes[block_index] > 0); + + m_block_store.positions.emplace(m_block_store.positions.begin(), 0); + m_block_store.sizes.emplace(m_block_store.sizes.begin(), 1); + m_block_store.element_blocks.emplace(m_block_store.element_blocks.begin(), nullptr); + + create_new_block_with_new_cell(0, cell); + + m_block_store.positions[1] = 1; + return begin(); + } + + if (size_type& blk_size = m_block_store.sizes[block_index]; pos_in_block == blk_size - 1) + { + // Insert into the last cell in block. + blk_size -= 1; + assert(blk_size > 0); + + m_block_store.positions.push_back(blk_size); + m_block_store.sizes.push_back(1); + m_block_store.element_blocks.push_back(nullptr); + + create_new_block_with_new_cell(block_index + 1, cell); + iterator ret = end(); + return --ret; + } + + // Insert into the middle of the block. + return set_cell_to_middle_of_block(block_index, pos_in_block, cell); + } + + // This topmost empty block is followed by a non-empty block. + + if (pos_in_block == 0) + { + // t|x |???|b + assert(block_index < m_block_store.positions.size() - 1); + + if (m_block_store.sizes[block_index] == 1) + { + // t|x|???| - Top empty block with only one cell size. + element_category_type cat = mdds_mtv_get_element_type(cell); + bool blk_next = is_next_block_of_type(block_index, cat); + if (blk_next) + { + // t|x|xxx|b - Remove this top empty block of size 1, and + // prepend the cell to the next block. + delete_element_block(block_index); + m_block_store.erase(block_index); + m_block_store.sizes[block_index] += 1; + m_block_store.positions[block_index] -= 1; + mdds_mtv_prepend_value(*m_block_store.element_blocks[block_index], cell); + } + else + { + // t|x|---|b + create_new_block_with_new_cell(block_index, cell); + } + } + else + { + // Shrink this topmost block by one and set the new value above it as a new block of size 1. + assert(block_index == 0); + assert(m_block_store.sizes[0] > 1); + m_block_store.sizes[0] -= 1; + m_block_store.positions[0] = 1; + + m_block_store.positions.emplace(m_block_store.positions.begin(), 0); + m_block_store.sizes.emplace(m_block_store.sizes.begin(), 1); + m_block_store.element_blocks.emplace(m_block_store.element_blocks.begin(), nullptr); + create_new_block_with_new_cell(0, cell); + } + + return begin(); + } + + if (pos_in_block == m_block_store.sizes[block_index] - 1) + { + // Set the cell to the last position of the block, immediately above + // a non-empty block. + element_category_type cat = mdds_mtv_get_element_type(cell); + bool blk_next = is_next_block_of_type(block_index, cat); + if (blk_next) + { + assert(m_block_store.sizes[block_index] > 1); + + // Shrink this empty block by one, and prepend the cell to the next block. + m_block_store.sizes[block_index] -= 1; + m_block_store.sizes[block_index + 1] += 1; + m_block_store.positions[block_index + 1] -= 1; + mdds_mtv_prepend_value(*m_block_store.element_blocks[block_index + 1], cell); + } + else + { + // Shrink the current empty block by one, and create a new block of size 1 to store the new value. + m_block_store.sizes[block_index] -= 1; + m_block_store.insert(block_index + 1, 1); + m_block_store.calc_block_position(block_index + 1); + m_block_store.sizes[block_index + 1] = 1; + + create_new_block_with_new_cell(block_index + 1, cell); + } + + return get_iterator(block_index + 1); + } + + // Inserting into the middle of an empty block. + return set_cell_to_middle_of_block(block_index, pos_in_block, cell); + } + + // This empty block is right below a non-empty block. + assert(block_index > 0 && m_block_store.element_blocks[block_index - 1] != nullptr); + + if (pos_in_block == 0) + { + // Set the value to the top of the block, right below a non-empty block. + element_category_type blk_cat_prev = mdds::mtv::get_block_type(*m_block_store.element_blocks[block_index - 1]); + element_category_type cat = mdds_mtv_get_element_type(cell); + + if (blk_cat_prev == cat) + { + // Extend the previous block by one to insert this cell. + if (m_block_store.sizes[block_index] == 1) + { + // Check if we need to merge with the following block. + if (block_index == m_block_store.positions.size() - 1) + { + // Last block. Delete this block and extend the previous + // block by one. + delete_element_block(block_index); + m_block_store.pop_back(); + append_cell_to_block(block_index - 1, cell); + } + else + { + // Block exists below. + bool blk_next = is_next_block_of_type(block_index, blk_cat_prev); + if (blk_next) + { + // Empty block must not be followed by another empty block. + assert(m_block_store.element_blocks[block_index + 1]); + + // We need to merge the previous and next blocks, then + // delete the current and next blocks. Be sure to + // resize the next block to zero to prevent the + // transferred cells to be deleted. + + // Check if the next block is bigger. + if (m_block_store.sizes[block_index - 1] < m_block_store.sizes[block_index + 1]) + { + // Prepend the new item to the next block, then + // prepend the content of the previous block and + // release both previous and current blocks. + + size_type position = m_block_store.positions[block_index - 1]; + + element_block_type* data = m_block_store.element_blocks[block_index]; + element_block_type* prev_data = m_block_store.element_blocks[block_index - 1]; + element_block_type* next_data = m_block_store.element_blocks[block_index + 1]; + + // Increase the size of block and prepend the new cell + m_block_store.sizes[block_index + 1] += 1; + mdds_mtv_prepend_value(*next_data, cell); + + // Preprend the content of previous block to the next block. + size_type prev_size = m_block_store.sizes[block_index - 1]; + block_funcs::prepend_values_from_block(*next_data, *prev_data, 0, prev_size); + m_block_store.sizes[block_index + 1] += prev_size; + m_block_store.positions[block_index + 1] = position; + + // Resize the previous block to zero + block_funcs::resize_block(*prev_data, 0); + m_hdl_event.element_block_released(prev_data); + + // Release both blocks which are no longer used + block_funcs::delete_block(data); + block_funcs::delete_block(prev_data); + + // Remove the previous and current blocks. + m_block_store.erase(block_index - 1, 2); + } + else + { + // Be sure to resize the next block to zero to prevent the + // transferred cells to be deleted. + m_block_store.sizes[block_index - 1] += 1 + m_block_store.sizes[block_index + 1]; + element_block_type* data = m_block_store.element_blocks[block_index]; + element_block_type* data_prev = m_block_store.element_blocks[block_index - 1]; + element_block_type* data_next = m_block_store.element_blocks[block_index + 1]; + mdds_mtv_append_value(*data_prev, cell); + block_funcs::append_block(*data_prev, *data_next); + block_funcs::resize_block(*data_next, 0); + m_hdl_event.element_block_released(data_next); + block_funcs::delete_block(data); + block_funcs::delete_block(data_next); + m_block_store.erase(block_index, 2); + } + } + else + { + // Ignore the next block. Just extend the previous block. + delete_element_block(block_index); + m_block_store.erase(block_index); + append_cell_to_block(block_index - 1, cell); + } + } + } + else + { + // Extend the previous block to append the cell. + assert(m_block_store.sizes[block_index] > 1); + m_block_store.sizes[block_index] -= 1; + m_block_store.positions[block_index] += 1; + append_cell_to_block(block_index - 1, cell); + } + + return get_iterator(block_index - 1); + } + else + { + // t|---|x |???|b - Cell type is different from the previous block's. + if (m_block_store.sizes[block_index] == 1) + { + // t|---|x|???|b + if (block_index == m_block_store.positions.size() - 1) + { + // t|---|x|b - There is no more block below. Simply turn this empty block into a non-empty one. + create_new_block_with_new_cell(block_index, cell); + } + else + { + // Check the type of the following non-empty block. + assert(block_index < m_block_store.positions.size() - 1); + bool blk_next = is_next_block_of_type(block_index, cat); + if (blk_next) + { + // t|---|x|xxx|b - Remove this empty block, and prepend the cell to the next block. + m_block_store.sizes[block_index + 1] += 1; + m_block_store.positions[block_index + 1] -= 1; + mdds_mtv_prepend_value(*m_block_store.element_blocks[block_index + 1], cell); + delete_element_block(block_index); + m_block_store.erase(block_index); + } + else + { + // t|---|x|---|b - Simply turn this empty block into a + // non-empty one. + create_new_block_with_new_cell(block_index, cell); + } + } + } + else + { + // t|---|x |???|b + + size_type new_block_size = m_block_store.sizes[block_index] - 1; + size_type new_block_position = m_block_store.positions[block_index] + 1; + m_block_store.sizes[block_index] = 1; + create_new_block_with_new_cell(block_index, cell); + m_block_store.insert(block_index + 1, new_block_position, new_block_size, nullptr); + } + + return get_iterator(block_index); + } + } + else if (pos_in_block == m_block_store.sizes[block_index] - 1) + { + // t|???| x|???|b - New cell is set to the end of the current block. + assert(m_block_store.sizes[block_index] > 1); + if (block_index == m_block_store.positions.size() - 1) + { + // t|???| x|b - The current block is the last block. + m_block_store.sizes[block_index] -= 1; + m_block_store.push_back(0, 1, nullptr); + m_block_store.calc_block_position(block_index + 1); + create_new_block_with_new_cell(block_index + 1, cell); + + iterator it = end(); + return --it; + } + else + { + // t|???| x|???|b - A non-empty block exists below. + element_category_type cat = mdds_mtv_get_element_type(cell); + bool blk_next = is_next_block_of_type(block_index, cat); + if (blk_next) + { + // t|???| x|xxx|b - Shrink this empty block and extend the next block. + m_block_store.sizes[block_index] -= 1; + m_block_store.sizes[block_index + 1] += 1; + m_block_store.positions[block_index + 1] -= 1; + mdds_mtv_prepend_value(*m_block_store.element_blocks[block_index + 1], cell); + } + else + { + // t|???| x|---|b - Shrink this block by one and insert a new block for the new cell. + m_block_store.sizes[block_index] -= 1; + m_block_store.insert(block_index + 1, 0, 1, nullptr); + m_block_store.calc_block_position(block_index + 1); + create_new_block_with_new_cell(block_index + 1, cell); + } + + return get_iterator(block_index + 1); + } + } + + // New cell is somewhere in the middle of an empty block. + return set_cell_to_middle_of_block(block_index, pos_in_block, cell); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cell_to_non_empty_block_of_size_one( + size_type block_index, const T& cell) +{ + assert(m_block_store.sizes[block_index] == 1); + assert(m_block_store.element_blocks[block_index]); + element_category_type cat = mdds_mtv_get_element_type(cell); + assert(mdds::mtv::get_block_type(*m_block_store.element_blocks[block_index]) != cat); + + if (block_index == 0) + { + // t|x|???|b - topmost block of size 1. + if (block_index == m_block_store.positions.size() - 1) + { + // t|x|b - This is the only block. + create_new_block_with_new_cell(block_index, cell); + return begin(); + } + + // There is a block below. + bool blk_next = is_next_block_of_type(block_index, cat); + if (!blk_next) + { + // t|x|---|b - Next block is of different type. + create_new_block_with_new_cell(block_index, cell); + return begin(); + } + + // t|x|xxx|b - Delete this block and prepend the cell to the next block. + m_block_store.sizes[block_index + 1] += 1; + m_block_store.positions[block_index + 1] -= 1; + mdds_mtv_prepend_value(*m_block_store.element_blocks[block_index + 1], cell); + delete_element_block(block_index); + m_block_store.erase(block_index); + + return begin(); + } + + assert(block_index > 0); + + if (block_index == m_block_store.positions.size() - 1) + { + // t|???|x|b - This is the last block and another block exists above. + element_block_type* prev_data = m_block_store.element_blocks[block_index - 1]; + if (!prev_data || mdds::mtv::get_block_type(*prev_data) != cat) + { + // t|---|x|b - The previous block is of different type. + create_new_block_with_new_cell(block_index, cell); + } + else + { + // t|xxx|x|b - Append the cell to the previous block and remove the current one. + mdds_mtv_append_value(*m_block_store.element_blocks[block_index - 1], cell); + m_block_store.sizes[block_index - 1] += 1; + delete_element_block(block_index); + m_block_store.erase(block_index); + } + + iterator itr = end(); + return --itr; + } + + // Remove the current block, and check if the cell can be append to the + // previous block, or prepended to the following block. Also check if the + // blocks above and below need to be combined. + + if (!m_block_store.element_blocks[block_index - 1]) + { + // t| |x|???|b - Previous block is empty. + if (!m_block_store.element_blocks[block_index + 1]) + { + // t| |x| |b - Next block is empty too. + create_new_block_with_new_cell(block_index, cell); + return get_iterator(block_index); + } + + // Previous block is empty, but the next block is not. + element_category_type blk_cat_next = mdds::mtv::get_block_type(*m_block_store.element_blocks[block_index + 1]); + + if (blk_cat_next == cat) + { + // t| |x|xxx|b - Next block is of the same type as the new value. + delete_element_block(block_index); + m_block_store.erase(block_index); + m_block_store.sizes[block_index] += 1; + m_block_store.positions[block_index] -= 1; + mdds_mtv_prepend_value(*m_block_store.element_blocks[block_index], cell); + return get_iterator(block_index); + } + + // t| |x|---|b + assert(blk_cat_next != cat); + create_new_block_with_new_cell(block_index, cell); + return get_iterator(block_index); + } + + if (!m_block_store.element_blocks[block_index + 1]) + { + // t|---|x| |b - Next block is empty and the previous block is not. + element_block_type* prev_data = m_block_store.element_blocks[block_index - 1]; + assert(prev_data); + element_category_type prev_cat = mdds::mtv::get_block_type(*prev_data); + + if (prev_cat == cat) + { + // t|xxx|x| |b - Append to the previous block. + m_block_store.sizes[block_index - 1] += 1; + mdds_mtv_append_value(*prev_data, cell); + delete_element_block(block_index); + m_block_store.erase(block_index); + return get_iterator(block_index - 1); + } + + // Just overwrite the current block. + create_new_block_with_new_cell(block_index, cell); + return get_iterator(block_index); + } + + // t|???|x|???|b - Neither previous nor next blocks are empty. + element_block_type* prev_data = m_block_store.element_blocks[block_index - 1]; + element_block_type* next_data = m_block_store.element_blocks[block_index + 1]; + assert(prev_data); + assert(next_data); + element_category_type prev_cat = mdds::mtv::get_block_type(*prev_data); + element_category_type next_cat = mdds::mtv::get_block_type(*next_data); + + if (prev_cat == next_cat) + { + if (prev_cat == cat) + { + // t|xxx|x|xxx|b - All three blocks are of the same type. Merge all + // three blocks. + m_block_store.sizes[block_index - 1] += 1 + m_block_store.sizes[block_index + 1]; + mdds_mtv_append_value(*prev_data, cell); + block_funcs::append_block(*prev_data, *next_data); + block_funcs::resize_block(*next_data, 0); // to prevent deletion of managed cells on block deletion + + // Delete the current and next blocks. + delete_element_block(block_index); + delete_element_block(block_index + 1); + m_block_store.erase(block_index, 2); + + return get_iterator(block_index - 1); + } + + // t|---|x|---|b - Just overwrite the current block. + create_new_block_with_new_cell(block_index, cell); + return get_iterator(block_index); + } + + assert(prev_cat != next_cat); + + if (prev_cat == cat) + { + // t|xxx|x|---|b - Append to the previous block. + m_block_store.sizes[block_index - 1] += 1; + mdds_mtv_append_value(*m_block_store.element_blocks[block_index - 1], cell); + delete_element_block(block_index); + m_block_store.erase(block_index); + return get_iterator(block_index - 1); + } + + if (next_cat == cat) + { + // t|---|x|xxx|b - Prepend to the next block. + m_block_store.sizes[block_index + 1] += 1; + m_block_store.positions[block_index + 1] -= 1; + mdds_mtv_prepend_value(*m_block_store.element_blocks[block_index + 1], cell); + delete_element_block(block_index); + m_block_store.erase(block_index); + return get_iterator(block_index); + } + + // t|---|x|+++|b - Just overwrite the current block. + create_new_block_with_new_cell(block_index, cell); + return get_iterator(block_index); +} + +template +typename multi_type_vector::size_type multi_type_vector::size() const +{ + MDDS_MTV_TRACE(accessor); + + return m_cur_size; +} + +template +typename multi_type_vector::size_type multi_type_vector::block_size() const +{ + MDDS_MTV_TRACE(accessor); + + return m_block_store.positions.size(); +} + +template +bool multi_type_vector::empty() const +{ + MDDS_MTV_TRACE(accessor); + + return m_block_store.positions.empty(); +} + +template +template +void multi_type_vector::get(size_type pos, T& value) const +{ + MDDS_MTV_TRACE_ARGS(accessor, "pos=" << pos << "; value=? (type=" << mdds_mtv_get_element_type(value) << ")"); + get_impl(pos, value); +} + +template +template +T multi_type_vector::get(size_type pos) const +{ + MDDS_MTV_TRACE_ARGS(accessor, "pos=" << pos); + + T cell; + get_impl(pos, cell); + return cell; +} + +template +template +T multi_type_vector::release(size_type pos) +{ + MDDS_MTV_TRACE_ARGS(mutator, "pos=" << pos); + + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + T value; + release_impl(pos, block_index, value); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in release(pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return value; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::release(size_type pos, T& value) +{ + MDDS_MTV_TRACE_ARGS(mutator, "pos=" << pos << "; value=? (type=" << mdds_mtv_get_element_type(value) << ")"); + + size_type block_index = get_block_position(pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + auto ret = release_impl(pos, block_index, value); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in release(pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +template +typename multi_type_vector::iterator multi_type_vector::release( + const iterator& pos_hint, size_type pos, T& value) +{ + MDDS_MTV_TRACE_ARGS( + mutator_with_pos_hint, + "pos_hint=" << pos_hint << "; pos=" << pos << "; value=? (type=" << mdds_mtv_get_element_type(value) << ")"); + + size_type block_index = get_block_position(pos_hint->__private_data, pos); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release", __LINE__, pos, block_size(), size()); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + auto ret = release_impl(pos, block_index, value); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in release(pos=" << pos << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif + + return ret; +} + +template +void multi_type_vector::release() +{ + MDDS_MTV_TRACE(mutator); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + for (auto* data : m_block_store.element_blocks) + { + if (!data) + continue; + + block_funcs::resize_block(*data, 0); + m_hdl_event.element_block_released(data); + block_funcs::delete_block(data); + } + + m_block_store.clear(); + m_cur_size = 0; + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in release()" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +typename multi_type_vector::iterator multi_type_vector::release_range( + size_type start_pos, size_type end_pos) +{ + MDDS_MTV_TRACE_ARGS(mutator, "start_pos=" << start_pos << "; end_pos=" << end_pos); + + size_type block_index1 = get_block_position(start_pos); + if (block_index1 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release_range", __LINE__, start_pos, block_size(), size()); + + return set_empty_impl(start_pos, end_pos, block_index1, false); +} + +template +typename multi_type_vector::iterator multi_type_vector::release_range( + const iterator& pos_hint, size_type start_pos, size_type end_pos) +{ + MDDS_MTV_TRACE_ARGS( + mutator_with_pos_hint, "pos_hint=" << pos_hint << "; start_pos=" << start_pos << "; end_pos=" << end_pos); + + size_type block_index1 = get_block_position(pos_hint->__private_data, start_pos); + if (block_index1 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::release_range", __LINE__, start_pos, block_size(), size()); + + return set_empty_impl(start_pos, end_pos, block_index1, false); +} + +template +typename multi_type_vector::iterator multi_type_vector::begin() +{ + MDDS_MTV_TRACE(accessor); + + return iterator( + {m_block_store.positions.begin(), m_block_store.sizes.begin(), m_block_store.element_blocks.begin()}, + {m_block_store.positions.end(), m_block_store.sizes.end(), m_block_store.element_blocks.end()}, this, 0); +} + +template +typename multi_type_vector::iterator multi_type_vector::end() +{ + MDDS_MTV_TRACE(accessor); + + return iterator( + {m_block_store.positions.end(), m_block_store.sizes.end(), m_block_store.element_blocks.end()}, + {m_block_store.positions.end(), m_block_store.sizes.end(), m_block_store.element_blocks.end()}, this, + m_block_store.positions.size()); +} + +template +typename multi_type_vector::const_iterator multi_type_vector::begin() const +{ + MDDS_MTV_TRACE(accessor); + + return cbegin(); +} + +template +typename multi_type_vector::const_iterator multi_type_vector::end() const +{ + MDDS_MTV_TRACE(accessor); + + return cend(); +} + +template +typename multi_type_vector::const_iterator multi_type_vector::cbegin() const +{ + MDDS_MTV_TRACE(accessor); + + return const_iterator( + {m_block_store.positions.cbegin(), m_block_store.sizes.cbegin(), m_block_store.element_blocks.cbegin()}, + {m_block_store.positions.cend(), m_block_store.sizes.cend(), m_block_store.element_blocks.cend()}, this, 0); +} + +template +typename multi_type_vector::const_iterator multi_type_vector::cend() const +{ + MDDS_MTV_TRACE(accessor); + + return const_iterator( + {m_block_store.positions.cend(), m_block_store.sizes.cend(), m_block_store.element_blocks.cend()}, + {m_block_store.positions.cend(), m_block_store.sizes.cend(), m_block_store.element_blocks.cend()}, this, + m_block_store.positions.size()); +} + +template +typename multi_type_vector::reverse_iterator multi_type_vector::rbegin() +{ + MDDS_MTV_TRACE(accessor); + + return reverse_iterator( + {m_block_store.positions.rbegin(), m_block_store.sizes.rbegin(), m_block_store.element_blocks.rbegin()}, + {m_block_store.positions.rend(), m_block_store.sizes.rend(), m_block_store.element_blocks.rend()}, this, 0); +} + +template +typename multi_type_vector::reverse_iterator multi_type_vector::rend() +{ + MDDS_MTV_TRACE(accessor); + + return reverse_iterator( + {m_block_store.positions.rend(), m_block_store.sizes.rend(), m_block_store.element_blocks.rend()}, + {m_block_store.positions.rend(), m_block_store.sizes.rend(), m_block_store.element_blocks.rend()}, this, 0); +} + +template +typename multi_type_vector::const_reverse_iterator multi_type_vector::rbegin() const +{ + MDDS_MTV_TRACE(accessor); + + return crbegin(); +} + +template +typename multi_type_vector::const_reverse_iterator multi_type_vector::rend() const +{ + MDDS_MTV_TRACE(accessor); + + return crend(); +} + +template +typename multi_type_vector::const_reverse_iterator multi_type_vector::crbegin() const +{ + MDDS_MTV_TRACE(accessor); + + return const_reverse_iterator( + {m_block_store.positions.rbegin(), m_block_store.sizes.rbegin(), m_block_store.element_blocks.rbegin()}, + {m_block_store.positions.rend(), m_block_store.sizes.rend(), m_block_store.element_blocks.rend()}, this, 0); +} + +template +typename multi_type_vector::const_reverse_iterator multi_type_vector::crend() const +{ + MDDS_MTV_TRACE(accessor); + + return const_reverse_iterator( + {m_block_store.positions.rend(), m_block_store.sizes.rend(), m_block_store.element_blocks.rend()}, + {m_block_store.positions.rend(), m_block_store.sizes.rend(), m_block_store.element_blocks.rend()}, this, 0); +} + +template +typename multi_type_vector::size_type multi_type_vector::get_block_position( + size_type row, size_type start_block_index) const +{ + if (row >= m_cur_size || start_block_index >= m_block_store.positions.size()) + return m_block_store.positions.size(); + + auto it0 = m_block_store.positions.begin(); + std::advance(it0, start_block_index); + + auto it = std::lower_bound(it0, m_block_store.positions.end(), row); + + if (it == m_block_store.positions.end() || *it != row) + { + // Binary search has overshot by one block. Move back one. + assert(it != it0); + --it; + } + + size_type pos = std::distance(it0, it) + start_block_index; + assert(*it <= row); + assert(row < *it + m_block_store.sizes[pos]); + return pos; +} + +template +typename multi_type_vector::size_type multi_type_vector::get_block_position( + const typename value_type::private_data& pos_data, size_type row) const +{ + size_type block_index = 0; + if (pos_data.parent == this && pos_data.block_index < m_block_store.positions.size()) + block_index = pos_data.block_index; + + size_type start_row = m_block_store.positions[block_index]; + + if (row < start_row) + { + // Position hint is past the insertion position. + // Walk back if that seems efficient. + if (row > start_row / 2) + { + for (size_type i = block_index; i > 0;) + { + --i; + start_row = m_block_store.positions[i]; + if (row >= start_row) + { + // Row is in this block. + return i; + } + // Specified row is not in this block. + } + assert(start_row == 0); + } + // Otherwise reset. + block_index = 0; + } + return get_block_position(row, block_index); +} + +template +template +void multi_type_vector::create_new_block_with_new_cell(size_type block_index, const T& cell) +{ + element_block_type* data = m_block_store.element_blocks[block_index]; + if (data) + { + m_hdl_event.element_block_released(data); + block_funcs::delete_block(data); + } + + // New cell block with size 1. + data = mdds_mtv_create_new_block(1, cell); + if (!data) + throw general_error("Failed to create new block."); + + m_hdl_event.element_block_acquired(data); + + m_block_store.element_blocks[block_index] = data; +} + +template +template +void multi_type_vector::append_cell_to_block(size_type block_index, const T& cell) +{ + m_block_store.sizes[block_index] += 1; + mdds_mtv_append_value(*m_block_store.element_blocks[block_index], cell); +} + +template +template +bool multi_type_vector::append_to_prev_block( + size_type block_index, element_category_type cat, size_type length, const T& it_begin, const T& it_end) +{ + bool blk_prev = is_previous_block_of_type(block_index, cat); + if (!blk_prev) + return false; + + // Append to the previous block. + mdds_mtv_append_values(*m_block_store.element_blocks[block_index - 1], *it_begin, it_begin, it_end); + m_block_store.sizes[block_index - 1] += length; + return true; +} + +template +template +void multi_type_vector::insert_cells_to_middle( + size_type row, size_type block_index, const T& it_begin, const T& it_end) +{ + size_type start_row = m_block_store.positions[block_index]; + size_type length = std::distance(it_begin, it_end); + element_category_type cat = mdds_mtv_get_element_type(*it_begin); + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + + // Insert two new blocks after the specified block position. + size_type n1 = row - start_row; + size_type n2 = m_block_store.sizes[block_index] - n1; + m_block_store.insert(block_index + 1, 2u); + + m_block_store.sizes[block_index] = n1; + m_block_store.sizes[block_index + 1] = length; + m_block_store.sizes[block_index + 2] = n2; + m_block_store.calc_block_position(block_index + 1); + m_block_store.calc_block_position(block_index + 2); + + // block for data series. + m_block_store.element_blocks[block_index + 1] = block_funcs::create_new_block(cat, 0); + element_block_type* blk2_data = m_block_store.element_blocks[block_index + 1]; + m_hdl_event.element_block_acquired(blk2_data); + mdds_mtv_assign_values(*blk2_data, *it_begin, it_begin, it_end); + + if (blk_data) + { + element_category_type blk_cat = mdds::mtv::get_block_type(*blk_data); + + // block to hold data from the lower part of the existing block. + m_block_store.element_blocks[block_index + 2] = block_funcs::create_new_block(blk_cat, 0); + element_block_type* blk3_data = m_block_store.element_blocks[block_index + 2]; + m_hdl_event.element_block_acquired(blk3_data); + + // Transfer the lower part of the current block to the new block. + size_type offset = row - start_row; + block_funcs::assign_values_from_block(*blk3_data, *blk_data, offset, n2); + block_funcs::resize_block(*blk_data, m_block_store.sizes[block_index]); + } + + adjust_block_positions_func{}(m_block_store, block_index + 3, length); +} + +template +template +typename multi_type_vector::iterator multi_type_vector::set_cell_to_middle_of_block( + size_type block_index, size_type pos_in_block, const T& cell) +{ + block_index = set_new_block_to_middle(block_index, pos_in_block, 1, true); + create_new_block_with_new_cell(block_index, cell); + + // Return the iterator referencing the inserted block. + return get_iterator(block_index); +} + +template +template +void multi_type_vector::set_cell_to_top_of_data_block(size_type block_index, const T& cell) +{ + // t|---|x--|???|b + + m_block_store.sizes[block_index] -= 1; + size_type position = m_block_store.positions[block_index]; + m_block_store.positions[block_index] += 1; + + element_block_type* data = m_block_store.element_blocks[block_index]; + if (data) + { + block_funcs::overwrite_values(*data, 0, 1); + block_funcs::erase(*data, 0); + } + + m_block_store.insert(block_index, position, 1, nullptr); + create_new_block_with_new_cell(block_index, cell); +} + +template +template +void multi_type_vector::set_cell_to_bottom_of_data_block(size_type block_index, const T& cell) +{ + // Erase the last value of the block. + assert(block_index < m_block_store.positions.size()); + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + size_type& blk_size = m_block_store.sizes[block_index]; + if (blk_data) + { + block_funcs::overwrite_values(*blk_data, blk_size - 1, 1); + block_funcs::erase(*blk_data, blk_size - 1); + } + blk_size -= 1; + + // Insert a new block of size one with the new value. + m_block_store.insert(block_index + 1, 0, 1, nullptr); + m_block_store.calc_block_position(block_index + 1); + create_new_block_with_new_cell(block_index + 1, cell); +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer_impl( + size_type start_pos, size_type end_pos, size_type block_index1, multi_type_vector& dest, size_type dest_pos) +{ + if (start_pos > end_pos) + { + std::ostringstream os; + os << "multi_type_vector::transfer_impl: start position is larger than the end position. (start="; + os << start_pos << ", end=" << end_pos << ")"; + throw std::out_of_range(os.str()); + } + + size_type block_index2 = get_block_position(end_pos, block_index1); + if (block_index2 == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::transfer_impl", __LINE__, end_pos, block_size(), size()); + + size_type len = end_pos - start_pos + 1; + size_type last_dest_pos = dest_pos + len - 1; + + // Make sure the destination container is large enough. + if (last_dest_pos >= dest.size()) + throw std::out_of_range("Destination vector is too small for the elements being transferred."); + + if (block_index1 == block_index2) + { + // All elements are in the same block. + return transfer_single_block(start_pos, end_pos, block_index1, dest, dest_pos); + } + + return transfer_multi_blocks(start_pos, end_pos, block_index1, block_index2, dest, dest_pos); +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer_single_block( + size_type start_pos, size_type end_pos, size_type block_index1, multi_type_vector& dest, size_type dest_pos) +{ + size_type len = end_pos - start_pos + 1; + size_type last_dest_pos = dest_pos + len - 1; + + // All elements are in the same block. + element_block_type* src_data = m_block_store.element_blocks[block_index1]; + size_type start_pos_in_block1 = m_block_store.positions[block_index1]; + + // Empty the region in the destination instance where the source elements + // are to be transferred to. This also ensures that the destination region + // consists of a single block. + iterator it_dest_blk = dest.set_empty(dest_pos, last_dest_pos); + + if (!src_data) + return get_iterator(block_index1); + + element_category_type cat = get_block_type(*src_data); + + size_type dest_block_index = it_dest_blk->__private_data.block_index; + element_block_type* dst_data = dest.m_block_store.element_blocks[dest_block_index]; + + size_type dest_pos_in_block = dest_pos - it_dest_blk->position; + if (dest_pos_in_block == 0) + { + // Copy to the top part of the destination block. + + assert(!dst_data); // should be already emptied. + size_type dst_size = dest.m_block_store.sizes[dest_block_index]; + if (len < dst_size) + { + // Shrink the existing block and insert a new block before it. + size_type position = dest.m_block_store.positions[dest_block_index]; + dest.m_block_store.positions[dest_block_index] += len; + dest.m_block_store.sizes[dest_block_index] -= len; + dest.m_block_store.insert(dest_block_index, position, len, nullptr); + } + } + else if (dest_pos_in_block + len - 1 == it_dest_blk->size - 1) + { + // Copy to the bottom part of destination block. + + // Insert a new block below current, and shrink the current block. + dest.m_block_store.sizes[dest_block_index] -= len; + dest.m_block_store.insert(dest_block_index + 1, 0, len, nullptr); + dest.m_block_store.calc_block_position(dest_block_index + 1); + ++dest_block_index; // Must point to the new copied block. + } + else + { + // Copy to the middle of the destination block. + + // Insert two new blocks below current. + size_type blk2_size = dest.m_block_store.sizes[dest_block_index] - dest_pos_in_block - len; + dest.m_block_store.insert(dest_block_index + 1, 2); + dest.m_block_store.sizes[dest_block_index] = dest_pos_in_block; + dest.m_block_store.sizes[dest_block_index + 1] = len; + dest.m_block_store.sizes[dest_block_index + 2] = blk2_size; + + dest.m_block_store.calc_block_position(dest_block_index + 1); + dest.m_block_store.calc_block_position(dest_block_index + 2); + + ++dest_block_index; // Must point to the new copied block. + } + + assert(dest.m_block_store.sizes[dest_block_index] == len); + size_type offset = start_pos - start_pos_in_block1; + if (offset == 0 && len == m_block_store.sizes[block_index1]) + { + // Just move the whole element block. + dest.m_block_store.element_blocks[dest_block_index] = src_data; + dest.m_hdl_event.element_block_acquired(src_data); + + m_hdl_event.element_block_released(src_data); + m_block_store.element_blocks[block_index1] = nullptr; + + dest.merge_with_adjacent_blocks(dest_block_index); + size_type start_pos_offset = merge_with_adjacent_blocks(block_index1); + if (start_pos_offset) + { + // Merged with the previous block. Adjust the return block position. + --block_index1; + start_pos_in_block1 -= start_pos_offset; + } + + return get_iterator(block_index1); + } + + dest.m_block_store.element_blocks[dest_block_index] = block_funcs::create_new_block(cat, 0); + dst_data = dest.m_block_store.element_blocks[dest_block_index]; + assert(dst_data); + dest.m_hdl_event.element_block_acquired(dst_data); + + // Shallow-copy the elements to the destination block. + block_funcs::assign_values_from_block(*dst_data, *src_data, offset, len); + dest.merge_with_adjacent_blocks(dest_block_index); + + // Set the source range empty without overwriting the elements. + return set_empty_in_single_block(start_pos, end_pos, block_index1, false); +} + +template +typename multi_type_vector::size_type multi_type_vector::merge_with_adjacent_blocks( + size_type block_index) +{ + assert(!m_block_store.positions.empty()); + assert(block_index < m_block_store.positions.size()); + + if (block_index == 0) + { + // No previous block. + merge_with_next_block(block_index); + return 0; + } + + size_type size_prev = m_block_store.sizes[block_index - 1]; + element_block_type* prev_data = m_block_store.element_blocks[block_index - 1]; + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + bool has_next = block_index < (m_block_store.element_blocks.size() - 1); + element_block_type* next_data = has_next ? m_block_store.element_blocks[block_index + 1] : nullptr; + + // Check the previous block. + if (prev_data) + { + // Previous block has data. + element_category_type cat_prev = mtv::get_block_type(*prev_data); + if (!blk_data || cat_prev != mtv::get_block_type(*blk_data)) + { + // Current block is empty or is of different type from the previous one. + merge_with_next_block(block_index); + return 0; + } + + // Previous and current blocks are of the same type. + if (next_data && cat_prev == get_block_type(*next_data)) + { + // Merge all three blocks. + m_block_store.sizes[block_index - 1] += + m_block_store.sizes[block_index] + m_block_store.sizes[block_index + 1]; + block_funcs::append_block(*prev_data, *blk_data); + block_funcs::append_block(*prev_data, *next_data); + + // Avoid overwriting the transferred elements. + block_funcs::resize_block(*blk_data, 0); + block_funcs::resize_block(*next_data, 0); + + delete_element_block(block_index); + delete_element_block(block_index + 1); + + m_block_store.erase(block_index, 2); + return size_prev; + } + + // Merge only the previous and current blocks. + bool merged = merge_with_next_block(block_index - 1); + if (!merged) + assert(!"Blocks were not merged!"); + + return size_prev; + } + + assert(!prev_data); // Previous block is empty. + + if (blk_data) + { + // Current block is not empty. Check with the next block. + merge_with_next_block(block_index); + return 0; + } + + // Previous and current blocks are both empty. + assert(!blk_data); + + if (has_next && !next_data) + { + // Next block is empty too. Merge all three. + m_block_store.sizes[block_index - 1] += m_block_store.sizes[block_index] + m_block_store.sizes[block_index + 1]; + + m_block_store.erase(block_index, 2); + + return size_prev; + } + + // Next block is not empty, or does not exist. Merge the current block with the previous one. + bool merged = merge_with_next_block(block_index - 1); + if (!merged) + assert(!"Blocks were not merged!"); + + return size_prev; +} + +template +bool multi_type_vector::merge_with_next_block(size_type block_index) +{ + assert(!m_block_store.positions.empty()); + assert(block_index < m_block_store.positions.size()); + + if (block_index >= m_block_store.positions.size() - 1) + // No more block below this one. + return false; + + // Block exists below. + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + element_block_type* next_data = m_block_store.element_blocks[block_index + 1]; + + if (!blk_data) + { + // Empty block. Merge only if the next block is also empty. + if (next_data) + // Next block is not empty. + return false; + + // Merge the two blocks. + m_block_store.sizes[block_index] += m_block_store.sizes[block_index + 1]; + m_block_store.erase(block_index + 1); + return true; + } + + if (!next_data) + return false; + + if (mdds::mtv::get_block_type(*blk_data) != mdds::mtv::get_block_type(*next_data)) + // Block types differ. Don't merge. + return false; + + // Merge it with the next block. + block_funcs::append_block(*blk_data, *next_data); + block_funcs::resize_block(*next_data, 0); + m_block_store.sizes[block_index] += m_block_store.sizes[block_index + 1]; + delete_element_block(block_index + 1); + m_block_store.erase(block_index + 1); + return true; +} + +template +typename multi_type_vector::size_type multi_type_vector::set_new_block_to_middle( + size_type block_index, size_type offset, size_type new_block_size, bool overwrite) +{ + assert(block_index < m_block_store.positions.size()); + + // First, insert two new blocks after the current block. + size_type lower_block_size = m_block_store.sizes[block_index] - offset - new_block_size; + m_block_store.insert(block_index + 1, 2); + m_block_store.sizes[block_index + 1] = new_block_size; // empty block. + m_block_store.sizes[block_index + 2] = lower_block_size; + + element_block_type* blk_data = m_block_store.element_blocks[block_index]; + if (blk_data) + { + size_type lower_data_start = offset + new_block_size; + assert(m_block_store.sizes[block_index + 2] == lower_block_size); + element_category_type cat = mtv::get_block_type(*blk_data); + m_block_store.element_blocks[block_index + 2] = block_funcs::create_new_block(cat, 0); + m_hdl_event.element_block_acquired(m_block_store.element_blocks[block_index + 2]); + + // Try to copy the fewer amount of data to the new non-empty block. + if (offset > lower_block_size) + { + // Keep the upper values in the current block and copy the lower + // values to the new non-empty block. + block_funcs::assign_values_from_block( + *m_block_store.element_blocks[block_index + 2], *blk_data, lower_data_start, lower_block_size); + + if (overwrite) + { + // Overwrite cells that will become empty. + block_funcs::overwrite_values(*blk_data, offset, new_block_size); + } + + // Shrink the current data block. + block_funcs::resize_block(*blk_data, offset); + m_block_store.sizes[block_index] = offset; + m_block_store.sizes[block_index + 2] = lower_block_size; + } + else + { + // Keep the lower values in the current block and copy the upper + // values to the new non-empty block (blk_lower), and swap the two + // later. + element_block_type* blk_lower_data = m_block_store.element_blocks[block_index + 2]; + block_funcs::assign_values_from_block(*blk_lower_data, *blk_data, 0, offset); + m_block_store.sizes[block_index + 2] = offset; + + if (overwrite) + { + // Overwrite cells that will become empty. + block_funcs::overwrite_values(*blk_data, offset, new_block_size); + } + + // Remove the upper and middle values and push the rest to the top. + block_funcs::erase(*blk_data, 0, lower_data_start); + + // Set the size of the current block to its new size ( what is after the new block ) + m_block_store.sizes[block_index] = lower_block_size; + m_block_store.sizes[block_index + 2] = offset; + + // And now let's swap the blocks, while preserving the position of the original block. + size_type position = m_block_store.positions[block_index]; + m_block_store.swap(block_index, block_index + 2); + m_block_store.positions[block_index] = position; + } + } + else + { + // There is no data, we just need to update the size of the block + m_block_store.sizes[block_index] = offset; + } + + // Re-calculate the block positions. + m_block_store.calc_block_position(block_index + 1); + m_block_store.calc_block_position(block_index + 2); + + return block_index + 1; +} + +template +bool multi_type_vector::is_previous_block_of_type(size_type block_index, element_category_type cat) const +{ + if (block_index == 0) + // No previous block. + return false; + + const element_block_type* data = m_block_store.element_blocks[block_index - 1]; + if (data) + return cat == mdds::mtv::get_block_type(*data); + + return cat == mtv::element_type_empty; +} + +template +bool multi_type_vector::is_next_block_of_type(size_type block_index, element_category_type cat) const +{ + if (block_index == m_block_store.positions.size() - 1) + // No next block. + return false; + + const element_block_type* data = m_block_store.element_blocks[block_index + 1]; + if (data) + return cat == mdds::mtv::get_block_type(*data); + + return cat == mtv::element_type_empty; +} + +template +typename multi_type_vector::element_block_type* multi_type_vector::exchange_elements( + const element_block_type& src_data, size_type src_offset, size_type dst_index, size_type dst_offset, size_type len) +{ + assert(dst_index < m_block_store.positions.size()); + element_block_type* dst_blk_data = m_block_store.element_blocks[dst_index]; + assert(dst_blk_data); + size_type dst_blk_size = m_block_store.sizes[dst_index]; + element_category_type cat_src = mtv::get_block_type(src_data); + bool blk_next = is_next_block_of_type(dst_index, cat_src); + + if (dst_offset == 0) + { + // Set elements to the top of the destination block. + bool blk_prev = is_previous_block_of_type(dst_index, cat_src); + + if (dst_blk_size == len) + { + // The whole block will get replaced. + std::unique_ptr data(dst_blk_data); + m_hdl_event.element_block_released(dst_blk_data); + m_block_store.element_blocks[dst_index] = + nullptr; // Prevent its deletion when the parent block gets deleted. + dst_blk_data = nullptr; + + if (blk_prev) + { + // Append to the previous block. Remove the current block. + element_block_type* dst_prev_data = m_block_store.element_blocks[dst_index - 1]; + block_funcs::append_values_from_block(*dst_prev_data, src_data, src_offset, len); + m_block_store.sizes[dst_index - 1] += len; + + size_type dst_erase_size = 1; + + // no need to call delete_block since dst_blk_data is null. + + if (blk_next) + { + // Apend elements from the next block too. + element_block_type* dst_next_data = m_block_store.element_blocks[dst_index + 1]; + block_funcs::append_block(*dst_prev_data, *dst_next_data); + m_block_store.sizes[dst_index - 1] += m_block_store.sizes[dst_index + 1]; + ++dst_erase_size; + delete_element_block(dst_index + 1); + } + + m_block_store.erase(dst_index, dst_erase_size); + return data.release(); + } + + // Check the next block to see if we need to merge. + if (blk_next) + { + // We need to merge with the next block. Remove the current + // block and use the next block to store the new elements as + // well as the existing ones. + element_block_type* dst_next_data = m_block_store.element_blocks[dst_index + 1]; + block_funcs::prepend_values_from_block(*dst_next_data, src_data, src_offset, len); + m_block_store.positions[dst_index + 1] -= len; + m_block_store.sizes[dst_index + 1] += len; + m_block_store.erase(dst_index); + } + else + { + dst_blk_data = block_funcs::create_new_block(cat_src, 0); + m_block_store.element_blocks[dst_index] = dst_blk_data; + m_hdl_event.element_block_acquired(dst_blk_data); + assert(dst_blk_data && dst_blk_data != data.get()); + block_funcs::assign_values_from_block(*dst_blk_data, src_data, src_offset, len); + } + + // Return this data block as-is. + return data.release(); + } + + // New block to send back to the caller. + std::unique_ptr data(nullptr); + + if (dst_blk_data) + { + element_category_type cat_dst = mtv::get_block_type(*dst_blk_data); + data.reset(block_funcs::create_new_block(cat_dst, 0)); + + // We need to keep the tail elements of the current block. + block_funcs::assign_values_from_block(*data, *dst_blk_data, 0, len); + block_funcs::erase(*dst_blk_data, 0, len); + } + + size_type position = m_block_store.positions[dst_index]; + m_block_store.positions[dst_index] += len; + m_block_store.sizes[dst_index] -= len; + + if (blk_prev) + { + // Append the new elements to the previous block. + element_block_type* dst_prev_data = m_block_store.element_blocks[dst_index - 1]; + block_funcs::append_values_from_block(*dst_prev_data, src_data, src_offset, len); + m_block_store.sizes[dst_index - 1] += len; + } + else + { + // Insert a new block to house the new elements. + m_block_store.insert(dst_index, position, len, nullptr); + dst_blk_data = block_funcs::create_new_block(cat_src, 0); + m_block_store.element_blocks[dst_index] = dst_blk_data; + m_hdl_event.element_block_acquired(dst_blk_data); + block_funcs::assign_values_from_block(*dst_blk_data, src_data, src_offset, len); + } + + return data.release(); + } + + // New block to send back to the caller. + std::unique_ptr data(nullptr); + + if (dst_blk_data) + { + // Copy the elements of the current block to the block being returned. + element_category_type cat_dst = mtv::get_block_type(*dst_blk_data); + data.reset(block_funcs::create_new_block(cat_dst, 0)); + block_funcs::assign_values_from_block(*data, *dst_blk_data, dst_offset, len); + } + + assert(dst_offset > 0); + size_type dst_end_pos = dst_offset + len; + + if (dst_end_pos == dst_blk_size) + { + // The new elements will replace the lower part of the block. + assert(dst_blk_data); + block_funcs::resize_block(*dst_blk_data, dst_offset); + m_block_store.sizes[dst_index] = dst_offset; + + if (blk_next) + { + // Merge with the next block. + element_block_type* dst_next_data = m_block_store.element_blocks[dst_index + 1]; + block_funcs::prepend_values_from_block(*dst_next_data, src_data, src_offset, len); + m_block_store.positions[dst_index + 1] -= len; + m_block_store.sizes[dst_index + 1] += len; + } + else + { + // Insert a new block to store the new elements. + size_type position = m_block_store.positions[dst_index] + dst_offset; + m_block_store.insert(dst_index + 1, position, len, nullptr); + m_block_store.element_blocks[dst_index + 1] = block_funcs::create_new_block(cat_src, 0); + dst_blk_data = m_block_store.element_blocks[dst_index + 1]; + assert(dst_blk_data); + m_hdl_event.element_block_acquired(dst_blk_data); + block_funcs::assign_values_from_block(*dst_blk_data, src_data, src_offset, len); + } + } + else + { + // The new elements will replace the middle of the block. + assert(dst_end_pos < m_block_store.sizes[dst_index]); + dst_index = set_new_block_to_middle(dst_index, dst_offset, len, false); + assert(m_block_store.sizes[dst_index] == len); + m_block_store.element_blocks[dst_index] = block_funcs::create_new_block(cat_src, 0); + dst_blk_data = m_block_store.element_blocks[dst_index]; + assert(dst_blk_data); + m_hdl_event.element_block_acquired(dst_blk_data); + block_funcs::assign_values_from_block(*dst_blk_data, src_data, src_offset, len); + } + + return data.release(); +} + +template +void multi_type_vector::exchange_elements( + const element_block_type& src_blk, size_type src_offset, size_type dst_index1, size_type dst_offset1, + size_type dst_index2, size_type dst_offset2, size_type len, blocks_type& new_blocks) +{ + assert(dst_index1 < dst_index2); + assert(dst_offset1 < m_block_store.sizes[dst_index1]); + assert(dst_offset2 < m_block_store.sizes[dst_index2]); + + blocks_to_transfer bucket; + prepare_blocks_to_transfer(bucket, dst_index1, dst_offset1, dst_index2, dst_offset2); + + m_block_store.insert(bucket.insert_index, 0, len, nullptr); + if (bucket.insert_index > 0) + m_block_store.calc_block_position(bucket.insert_index); + + m_block_store.element_blocks[bucket.insert_index] = block_funcs::create_new_block(mtv::get_block_type(src_blk), 0); + + element_block_type* blk_data = m_block_store.element_blocks[bucket.insert_index]; + + m_hdl_event.element_block_acquired(blk_data); + block_funcs::assign_values_from_block(*blk_data, src_blk, src_offset, len); + merge_with_adjacent_blocks(bucket.insert_index); + + new_blocks.swap(bucket.blocks); +} + +template +bool multi_type_vector::append_empty(size_type len) +{ + // Append empty cells. + if (m_block_store.positions.empty()) + { + // No existing block. Create a new one. + assert(m_cur_size == 0); + m_block_store.push_back(0, len, nullptr); + m_cur_size = len; + return true; + } + + bool new_block_added = false; + + element_block_type* last_data = m_block_store.element_blocks.back(); + + if (!last_data) + { + // Last block is empty. Just increase its size. + m_block_store.sizes.back() += len; + } + else + { + // Append a new empty block. + m_block_store.push_back(m_cur_size, len, nullptr); + new_block_added = true; + } + + m_cur_size += len; + + return new_block_added; +} + +template +void multi_type_vector::resize(size_type new_size) +{ + MDDS_MTV_TRACE_ARGS(mutator, "new_size=" << new_size); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block; + dump_blocks(os_prev_block); +#endif + + resize_impl(new_size); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + try + { + check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << "block integrity check failed in resize (new-size=" << new_size << ")" << std::endl; + os << "previous block state:" << std::endl; + os << os_prev_block.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +void multi_type_vector::resize_impl(size_type new_size) +{ + if (new_size == m_cur_size) + return; + + if (!new_size) + { + clear(); + return; + } + + if (new_size > m_cur_size) + { + // Append empty cells. + append_empty(new_size - m_cur_size); + return; + } + + assert(new_size < m_cur_size && new_size > 0); + + // Find out in which block the new end row will be. + size_type new_end_row = new_size - 1; + size_type block_index = get_block_position(new_end_row); + if (block_index == m_block_store.positions.size()) + mdds::mtv::detail::throw_block_position_not_found( + "multi_type_vector::resize", __LINE__, new_end_row, block_size(), size()); + + element_block_type* data = m_block_store.element_blocks[block_index]; + size_type start_row_in_block = m_block_store.positions[block_index]; + size_type end_row_in_block = start_row_in_block + m_block_store.sizes[block_index] - 1; + + if (new_end_row < end_row_in_block) + { + // Shrink the size of the current block. + size_type new_block_size = new_end_row - start_row_in_block + 1; + if (data) + { + block_funcs::overwrite_values(*data, new_end_row + 1, end_row_in_block - new_end_row); + block_funcs::resize_block(*data, new_block_size); + } + m_block_store.sizes[block_index] = new_block_size; + } + + // Remove all blocks below the current one. + delete_element_blocks(block_index + 1, m_block_store.element_blocks.size()); + size_type len = m_block_store.element_blocks.size() - block_index - 1; + m_block_store.erase(block_index + 1, len); + m_cur_size = new_size; +} + +template +typename multi_type_vector::iterator multi_type_vector::transfer_multi_blocks( + size_type start_pos, size_type end_pos, size_type block_index1, size_type block_index2, multi_type_vector& dest, + size_type dest_pos) +{ + assert(block_index1 < block_index2); + size_type start_pos_in_block1 = m_block_store.positions[block_index1]; + size_type start_pos_in_block2 = m_block_store.positions[block_index2]; + + size_type len = end_pos - start_pos + 1; + size_type last_dest_pos = dest_pos + len - 1; + + // Empty the region in the destination container where the elements + // are to be transferred to. This ensures that the destination region + // consists of a single block. + iterator it_dest_blk = dest.set_empty(dest_pos, last_dest_pos); + + size_type dest_block_index = it_dest_blk->__private_data.block_index; + size_type dest_pos_in_block = dest_pos - it_dest_blk->position; + assert(!dest.m_block_store.element_blocks[dest_block_index]); // should be already emptied. + + size_type block_len = block_index2 - block_index1 + 1; + + // Create slots for new blocks in the destination. + + size_type dest_block_index1 = dest_block_index; + + if (dest_pos_in_block == 0) + { + // Copy to the top part of destination block. + if (len < dest.m_block_store.sizes[dest_block_index]) + { + // Shrink the existing block and insert slots for the new blocks before it. + dest.m_block_store.sizes[dest_block_index] -= len; + dest.m_block_store.positions[dest_block_index] += len; + dest.m_block_store.insert(dest_block_index, block_len); + } + else + { + // Destination block is exactly of the length of the elements being transferred. + dest.delete_element_block(dest_block_index); + dest.m_block_store.sizes[dest_block_index] = 0; + if (block_len > 1) + dest.m_block_store.insert(dest_block_index, block_len - 1); + } + } + else if (dest_pos_in_block + len - 1 == it_dest_blk->size - 1) + { + // Copy to the bottom part of destination block. Insert slots for new + // blocks below current, and shrink the current block. + dest.m_block_store.insert(dest_block_index + 1, block_len); + dest.m_block_store.sizes[dest_block_index] -= len; + + ++dest_block_index1; + } + else + { + // Copy to the middle of the destination block. Insert slots for the + // new blocks (plus one extra for the bottom empty block) below the + // current block. + size_type blk2_size = dest.m_block_store.sizes[dest_block_index] - dest_pos_in_block - len; + dest.m_block_store.insert(dest_block_index + 1, block_len + 1); + assert(dest.m_block_store.positions.size() > dest_block_index + block_len + 1); + dest.m_block_store.sizes[dest_block_index] = dest_pos_in_block; + + // Re-calculate the size and position of the lower part of the destination block. + dest.m_block_store.positions[dest_block_index + block_len + 1] = + dest.m_block_store.calc_next_block_position(dest_block_index) + len; + dest.m_block_store.sizes[dest_block_index + block_len + 1] = blk2_size; + + ++dest_block_index1; + } + + size_type del_index1 = block_index1, del_index2 = block_index2; + + // Now that the new slots have been created, start transferring the blocks. + + // Transfer the first block. + size_type offset = start_pos - start_pos_in_block1; + if (offset) + { + // Transfer the lower part of the first block. + + assert(dest.m_block_store.sizes[dest_block_index1] == 0); + dest.m_block_store.sizes[dest_block_index1] = m_block_store.sizes[block_index1] - offset; + if (dest_block_index1 > 0) + dest.m_block_store.calc_block_position(dest_block_index1); + + if (m_block_store.element_blocks[block_index1]) + { + element_block_type* blk_data1 = m_block_store.element_blocks[block_index1]; + element_category_type cat = mtv::get_block_type(*blk_data1); + dest.m_block_store.element_blocks[dest_block_index1] = block_funcs::create_new_block(cat, 0); + element_block_type* dst_data1 = dest.m_block_store.element_blocks[dest_block_index1]; + assert(dst_data1); + dest.m_hdl_event.element_block_acquired(dst_data1); + + // Shallow-copy the elements to the destination block, and shrink + // the source block to remove the transferred elements. + block_funcs::assign_values_from_block( + *dst_data1, *blk_data1, offset, m_block_store.sizes[block_index1] - offset); + block_funcs::resize_block(*blk_data1, offset); + } + + m_block_store.sizes[block_index1] = offset; + ++del_index1; // Retain this block. + } + else + { + // Just move the whole block over. + element_block_type* data = m_block_store.element_blocks[block_index1]; + dest.m_block_store.element_blocks[dest_block_index1] = data; + dest.m_block_store.sizes[dest_block_index1] = m_block_store.sizes[block_index1]; + dest.m_block_store.calc_block_position(dest_block_index1); + + if (data) + { + dest.m_hdl_event.element_block_acquired(data); + m_hdl_event.element_block_released(data); + m_block_store.element_blocks[block_index1] = nullptr; + } + + m_block_store.sizes[block_index1] = 0; + } + + if (block_len > 2) + { + size_type position = dest.m_block_store.calc_next_block_position(dest_block_index1); + + for (size_type i = 0; i < block_len - 2; ++i) + { + size_type src_block_pos = block_index1 + 1 + i; + size_type dest_block_pos = dest_block_index1 + 1 + i; + assert(dest.m_block_store.sizes[dest_block_pos] == 0); + + element_block_type* data = m_block_store.element_blocks[src_block_pos]; + dest.m_block_store.element_blocks[dest_block_pos] = data; + dest.m_block_store.sizes[dest_block_pos] = m_block_store.sizes[src_block_pos]; + dest.m_block_store.positions[dest_block_pos] = position; + position += m_block_store.sizes[src_block_pos]; + m_block_store.sizes[src_block_pos] = 0; + + if (data) + { + dest.m_hdl_event.element_block_acquired(data); + m_hdl_event.element_block_released(data); + m_block_store.element_blocks[src_block_pos] = nullptr; + } + } + } + + // Transfer the last block. + if (block_len > 1) + { + size_type size_to_trans = end_pos - start_pos_in_block2 + 1; + size_type dest_block_pos = dest_block_index1 + block_len - 1; + assert(dest.m_block_store.sizes[dest_block_pos] == 0); + + element_block_type* blk_data2 = m_block_store.element_blocks[block_index2]; + + if (size_to_trans < m_block_store.sizes[block_index2]) + { + // Transfer the upper part of this block. + assert(dest_block_pos > 0); + dest.m_block_store.calc_block_position(dest_block_pos); + dest.m_block_store.sizes[dest_block_pos] = size_to_trans; + + if (blk_data2) + { + element_category_type cat = mtv::get_block_type(*blk_data2); + dest.m_block_store.element_blocks[dest_block_pos] = block_funcs::create_new_block(cat, 0); + element_block_type* blk_dst_data = dest.m_block_store.element_blocks[dest_block_pos]; + dest.m_hdl_event.element_block_acquired(blk_dst_data); + + block_funcs::assign_values_from_block(*blk_dst_data, *blk_data2, 0, size_to_trans); + block_funcs::erase(*blk_data2, 0, size_to_trans); + } + + m_block_store.positions[block_index2] += size_to_trans; + m_block_store.sizes[block_index2] -= size_to_trans; + --del_index2; // Retain this block. + } + else + { + // Just move the whole block over. + dest.m_block_store.sizes[dest_block_pos] = m_block_store.sizes[block_index2]; + dest.m_block_store.element_blocks[dest_block_pos] = m_block_store.element_blocks[block_index2]; + dest.m_block_store.calc_block_position(dest_block_pos); + + if (blk_data2) + { + dest.m_hdl_event.element_block_acquired(blk_data2); + m_hdl_event.element_block_released(blk_data2); + m_block_store.element_blocks[block_index2] = nullptr; + } + + m_block_store.sizes[block_index2] = 0; + } + } + + // Now that all the elements have been transferred, check the bordering + // blocks in the destination and merge them as needed. + if (block_len > 1) + dest.merge_with_adjacent_blocks(dest_block_index1 + block_len - 1); + dest.merge_with_adjacent_blocks(dest_block_index1); + + // Delete all transferred blocks, and replace it with one empty block. + if (del_index2 < del_index1) + { + // No blocks will be deleted. See if we can just extend one of the + // neighboring empty blocks. + + element_block_type* blk1_data = m_block_store.element_blocks[block_index1]; + element_block_type* blk2_data = m_block_store.element_blocks[block_index2]; + + if (!blk1_data) + { + assert(blk2_data); + + // Block 1 is empty. Extend this block downward. + m_block_store.sizes[block_index1] += len; + return get_iterator(block_index1); + } + + if (!blk2_data) + { + assert(blk1_data); + + // Block 2 is empty. Extend this block upward. + m_block_store.sizes[block_index2] += len; + m_block_store.positions[block_index2] -= len; + return get_iterator(block_index2); + } + + // Neither block1 nor block2 are empty. Just insert a new empty block + // between them. After the insertion, the old block2 position becomes + // the position of the inserted block. + m_block_store.insert(block_index2, 0, len, nullptr); + m_block_store.calc_block_position(block_index2); + + // No need to adjust local index vars + return get_iterator(block_index2); + } + + if (del_index1 > 0 && !m_block_store.element_blocks[del_index1 - 1]) + { + // The block before the first block to be deleted is empty. Simply + // extend that block to cover the deleted block segment. + m_block_store.sizes[del_index1 - 1] += len; + } + else + { + // Block before is not empty (or doesn't exist). Keep the first slot, + // and erase the rest. + m_block_store.sizes[del_index1] = len; // Insert an empty + ++del_index1; + } + + size_type ret_block_index = del_index1 - 1; + + if (del_index2 >= del_index1) + { + size_type n_del_blocks = del_index2 - del_index1 + 1; + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + for (size_type i = del_index1; i <= del_index2; ++i) + { + // All slots to be erased should have zero size + assert(m_block_store.sizes[i] == 0); + } +#endif + m_block_store.erase(del_index1, n_del_blocks); + } + + // The block pointed to by ret_block_index is guaranteed to be empty by + // this point. + assert(!m_block_store.element_blocks[ret_block_index]); + + // Merging with the previous block never happens. + size_type start_pos_offset = merge_with_adjacent_blocks(ret_block_index); + (void)start_pos_offset; // avoid unused variable compiler warning. + assert(!start_pos_offset); + + m_block_store.calc_block_position(ret_block_index); + return get_iterator(ret_block_index); +} + +template +void multi_type_vector::swap(multi_type_vector& other) +{ + MDDS_MTV_TRACE_ARGS(mutator, "other=?"); + + std::swap(m_hdl_event, other.m_hdl_event); + std::swap(m_cur_size, other.m_cur_size); + m_block_store.swap(other.m_block_store); +} + +template +void multi_type_vector::swap( + size_type start_pos, size_type end_pos, multi_type_vector& other, size_type other_pos) +{ + MDDS_MTV_TRACE_ARGS( + mutator, "start_pos=" << start_pos << "; end_pos=" << end_pos << "; other=?; other_pos=" << other_pos); + + if (start_pos > end_pos) + throw std::out_of_range("multi_type_vector::swap: start position is larger than the end position!"); + + size_type other_end_pos = other_pos + end_pos - start_pos; + + if (end_pos >= m_cur_size || other_end_pos >= other.m_cur_size) + throw std::out_of_range("multi_type_vector::swap: end position is out of bound!"); + + size_type block_index1 = get_block_position(start_pos); + if (block_index1 == m_block_store.positions.size()) + throw std::out_of_range("multi_type_vector::swap: start block position in source not found!"); + + size_type block_index2 = get_block_position(end_pos, block_index1); + if (block_index2 == m_block_store.positions.size()) + throw std::out_of_range("multi_type_vector::swap: end block position in source not found!"); + + size_type dest_block_index1 = other.get_block_position(other_pos); + if (dest_block_index1 == other.m_block_store.positions.size()) + throw std::out_of_range("multi_type_vector::swap: start block position in destination not found!"); + + size_type dest_block_index2 = other.get_block_position(other_end_pos, dest_block_index1); + if (dest_block_index2 == other.m_block_store.positions.size()) + throw std::out_of_range("multi_type_vector::swap: end block position in destination not found!"); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_prev_block, os_prev_block_other; + dump_blocks(os_prev_block); + other.dump_blocks(os_prev_block_other); +#endif + + swap_impl(other, start_pos, end_pos, other_pos, block_index1, block_index2, dest_block_index1, dest_block_index2); + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + std::ostringstream os_block, os_block_other; + dump_blocks(os_block); + other.dump_blocks(os_block_other); + + try + { + check_block_integrity(); + other.check_block_integrity(); + } + catch (const mdds::integrity_error& e) + { + std::ostringstream os; + os << e.what() << std::endl; + os << std::endl + << "block integrity check failed in swap (start_pos=" << start_pos << "; end_pos=" << end_pos + << "; other_pos=" << other_pos << ")" << std::endl; + os << std::endl << "previous block state (source):" << std::endl; + os << os_prev_block.str(); + os << std::endl << "previous block state (destination):" << std::endl; + os << os_prev_block_other.str(); + os << std::endl << "altered block state (source):" << std::endl; + os << os_block.str(); + os << std::endl << "altered block state (destination):" << std::endl; + os << os_block_other.str(); + std::cerr << os.str() << std::endl; + abort(); + } +#endif +} + +template +void multi_type_vector::shrink_to_fit() +{ + MDDS_MTV_TRACE(mutator); + + for (auto* data : m_block_store.element_blocks) + { + if (data) + block_funcs::shrink_to_fit(*data); + } +} + +template +bool multi_type_vector::operator==(const multi_type_vector& other) const +{ + MDDS_MTV_TRACE_ARGS(accessor, "other=?"); + + if (this == &other) + // Comparing to self is always equal. + return true; + + if (m_cur_size != other.m_cur_size) + // Row sizes differ. + return false; + + return m_block_store.equals(other.m_block_store); +} + +template +bool multi_type_vector::operator!=(const multi_type_vector& other) const +{ + MDDS_MTV_TRACE_ARGS(accessor, "other=?"); + + return !operator==(other); +} + +template +multi_type_vector& multi_type_vector::operator=(const multi_type_vector& other) +{ + MDDS_MTV_TRACE_ARGS(mutator, "other=? (copy)"); + + multi_type_vector assigned(other); + swap(assigned); + return *this; +} + +template +multi_type_vector& multi_type_vector::operator=(multi_type_vector&& other) +{ + MDDS_MTV_TRACE_ARGS(mutator, "other=? (move)"); + + multi_type_vector assigned(std::move(other)); + swap(assigned); + return *this; +} + +template +template +mtv::element_t multi_type_vector::get_element_type(const T& elem) +{ + return mdds_mtv_get_element_type(elem); +} + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + +template +void multi_type_vector::dump_blocks(std::ostream& os) const +{ + os << "--- blocks" << std::endl; + std::ios_base::fmtflags origflags = os.flags(); + for (size_type i = 0, n = m_block_store.positions.size(); i < n; ++i) + { + size_type pos = m_block_store.positions[i]; + size_type size = m_block_store.sizes[i]; + const element_block_type* data = m_block_store.element_blocks[i]; + element_category_type cat = mtv::element_type_empty; + if (data) + cat = mtv::get_block_type(*data); + os << " block " << i << ": position=" << pos << " size=" << size << " type=" << std::dec << cat + << " data=" << std::hex << data << std::endl; + } + os.setf(origflags); +} + +template +void multi_type_vector::check_block_integrity() const +{ + m_block_store.check_integrity(); + + if (m_block_store.positions.empty()) + // Nothing to check. + return; + + if (m_block_store.sizes.size() == 1 && m_block_store.sizes[0] == 0) + throw mdds::integrity_error("block should never be zero sized!"); + + if (m_block_store.positions[0] != 0u) + { + std::ostringstream os; + os << "position of the first block should be zero!" << std::endl; + + dump_blocks(os); + mdds::integrity_error(os.str()); + } + + const element_block_type* data_prev = m_block_store.element_blocks[0]; + element_category_type cat_prev = data_prev ? mtv::get_block_type(*data_prev) : mtv::element_type_empty; + + size_type cur_position = m_block_store.sizes[0]; + size_type total_size = m_block_store.sizes[0]; + + for (size_type i = 1, n = m_block_store.positions.size(); i < n; ++i) + { + size_type this_size = m_block_store.sizes[i]; + if (this_size == 0) + throw mdds::integrity_error("block should never be zero sized!"); + + if (m_block_store.positions[i] != cur_position) + { + std::ostringstream os; + os << "position of the current block is wrong! (expected=" << cur_position + << "; actual=" << m_block_store.positions[i] << ")" << std::endl; + + dump_blocks(os); + throw mdds::integrity_error(os.str()); + } + + element_category_type cat = mtv::element_type_empty; + + const element_block_type* data = m_block_store.element_blocks[i]; + + if (data) + { + cat = mtv::get_block_type(*data); + + if (block_funcs::size(*data) != this_size) + throw mdds::integrity_error("block size cache and the actual element block size differ!"); + } + + if (cat_prev == cat) + { + std::ostringstream os; + os << "Two adjacent blocks should never be of the same type." << std::endl; + dump_blocks(os); + mdds::integrity_error(os.str()); + } + + data_prev = data; + cat_prev = cat; + + total_size += this_size; + cur_position += this_size; + } + + if (total_size != m_cur_size) + { + std::ostringstream os; + os << "Current size does not equal the total sizes of all blocks." << std::endl; + os << "current size=" << m_cur_size << " total block size=" << total_size << std::endl; + dump_blocks(os); + mdds::integrity_error(os.str()); + } +} + +#endif + +}}} // namespace mdds::mtv::soa + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/standard_element_blocks.hpp b/include/mdds/multi_type_vector/standard_element_blocks.hpp new file mode 100644 index 0000000..a045ce8 --- /dev/null +++ b/include/mdds/multi_type_vector/standard_element_blocks.hpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2022 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ +#pragma once + +#include "types.hpp" +#include "util.hpp" +#include "block_funcs.hpp" +#include "macro.hpp" + +namespace mdds { namespace mtv { + +constexpr element_t element_type_boolean = element_type_reserved_start; +constexpr element_t element_type_int8 = element_type_reserved_start + 1; +constexpr element_t element_type_uint8 = element_type_reserved_start + 2; +constexpr element_t element_type_int16 = element_type_reserved_start + 3; +constexpr element_t element_type_uint16 = element_type_reserved_start + 4; +constexpr element_t element_type_int32 = element_type_reserved_start + 5; +constexpr element_t element_type_uint32 = element_type_reserved_start + 6; +constexpr element_t element_type_int64 = element_type_reserved_start + 7; +constexpr element_t element_type_uint64 = element_type_reserved_start + 8; +constexpr element_t element_type_float = element_type_reserved_start + 9; +constexpr element_t element_type_double = element_type_reserved_start + 10; +constexpr element_t element_type_string = element_type_reserved_start + 11; + +using boolean_element_block = default_element_block; +using int8_element_block = default_element_block; +using uint8_element_block = default_element_block; +using int16_element_block = default_element_block; +using uint16_element_block = default_element_block; +using int32_element_block = default_element_block; +using uint32_element_block = default_element_block; +using int64_element_block = default_element_block; +using uint64_element_block = default_element_block; +using float_element_block = default_element_block; +using double_element_block = default_element_block; +using string_element_block = default_element_block; + +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(bool, element_type_boolean, false, boolean_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(int8_t, element_type_int8, 0, int8_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(uint8_t, element_type_uint8, 0, uint8_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(int16_t, element_type_int16, 0, int16_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(uint16_t, element_type_uint16, 0, uint16_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(int32_t, element_type_int32, 0, int32_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(uint32_t, element_type_uint32, 0, uint32_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(int64_t, element_type_int64, 0, int64_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(uint64_t, element_type_uint64, 0, uint64_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(float, element_type_float, 0.0, float_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(double, element_type_double, 0.0, double_element_block) +MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(std::string, element_type_string, std::string(), string_element_block) + +struct standard_element_blocks_traits : public default_traits +{ + using block_funcs = element_block_funcs< + boolean_element_block, int8_element_block, uint8_element_block, int16_element_block, uint16_element_block, + int32_element_block, uint32_element_block, int64_element_block, uint64_element_block, float_element_block, + double_element_block, string_element_block>; +}; + +}} // namespace mdds::mtv + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/types.hpp b/include/mdds/multi_type_vector/types.hpp new file mode 100644 index 0000000..c51f31e --- /dev/null +++ b/include/mdds/multi_type_vector/types.hpp @@ -0,0 +1,1011 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_TYPES_2_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_TYPES_2_HPP + +#include "../global.hpp" +#include "./types_util.hpp" + +#include +#include +#include +#include +#include + +#if defined(MDDS_UNIT_TEST) || defined(MDDS_MULTI_TYPE_VECTOR_DEBUG) +#include +#include +using std::cerr; +using std::cout; +using std::endl; +#endif + +namespace mdds { namespace mtv { + +using element_t = int; + +constexpr element_t element_type_empty = -1; + +constexpr element_t element_type_reserved_start = 0; +constexpr element_t element_type_reserved_end = 49; + +constexpr element_t element_type_user_start = 50; + +/** + * Loop-unrolling factor with optional SIMD feature. + * + * In each enumerator value, the first byte contains the loop-unrolling factor + * (either 0, 4, 8, 16 or 32), while the second byte stores SIMD flags. + */ +enum class lu_factor_t : int +{ + none = 0, + lu4 = 4, + lu8 = 8, + lu16 = 16, + lu32 = 32, + sse2_x64 = 1 << 8, + sse2_x64_lu4 = 1 << 8 | 4, + sse2_x64_lu8 = 1 << 8 | 8, + sse2_x64_lu16 = 1 << 8 | 16, + avx2_x64 = 2 << 8, + avx2_x64_lu4 = 2 << 8 | 4, + avx2_x64_lu8 = 2 << 8 | 8, +}; + +/** + * Type of traced method. + * + * An accessor in this context is a method whose call alone does + * not mutate the state of the container. All const methods are accessors. + * Note that some non-const methods that return non-const references to + * internal data are still considered accessors. + * + * A mutator is a method that, when called, may change the state + * of the stored data immediately. + * + * The accessor_with_pos_hint label signifies an accessor that + * takes a position hint as its first argument. Likewise, + * mutator_with_pos_hint signifies a mutator that takes a + * position hint as its first argument. + * + * The constructor and destructor labels are + * hopefully self-explanatory. + */ +enum class trace_method_t : int +{ + unspecified = 0, + accessor = 1, + accessor_with_pos_hint = 1 << 8 | 1, + mutator = 2, + mutator_with_pos_hint = 1 << 8 | 2, + constructor = 3, + destructor = 4 +}; + +/** + * Struct containing the information about each traced method. + */ +struct trace_method_properties_t +{ + trace_method_t type = trace_method_t::unspecified; + + /** + * Memory address of the container instance the traced method belongs to. + * This is essentially the this pointer inside the traced + * method. + */ + const void* instance = nullptr; + + /** Name of the method. */ + const char* function_name = nullptr; + + /** + * String containing the argument names as well as their values if + * available. + */ + std::string function_args; + + /** Path of the file where the method body is. */ + const char* filepath = nullptr; + + /** Line number of the first line of the traced method body. */ + int line_number = -1; +}; + +/** + * Generic exception used for errors specific to element block operations. + */ +class element_block_error : public mdds::general_error +{ +public: + element_block_error(const std::string& msg) : mdds::general_error(msg) + {} +}; + +class base_element_block; +element_t get_block_type(const base_element_block&); + +/** + * Non-template common base type necessary for blocks of all types to be + * stored in a single container. + */ +class base_element_block +{ + friend element_t get_block_type(const base_element_block&); + +protected: + element_t type; + base_element_block(element_t _t) : type(_t) + {} +}; + +/** + * Vector that delays deleting from the front of the vector, which avoids + * O(n^2) memory move operations when code needs to delete items from one + * element block and add to another element block. + */ +template> +class delayed_delete_vector +{ + typedef std::vector store_type; + store_type m_vec; + size_t m_front_offset = 0; // number of elements removed from front of array +public: + typedef typename store_type::value_type value_type; + typedef typename store_type::size_type size_type; + typedef typename store_type::difference_type difference_type; + typedef typename store_type::reference reference; + typedef typename store_type::const_reference const_reference; + typedef typename store_type::pointer pointer; + typedef typename store_type::const_pointer const_pointer; + typedef typename store_type::iterator iterator; + typedef typename store_type::reverse_iterator reverse_iterator; + typedef typename store_type::const_iterator const_iterator; + typedef typename store_type::const_reverse_iterator const_reverse_iterator; + + delayed_delete_vector() : m_vec() + {} + + delayed_delete_vector(size_t n, const T& val) : m_vec(n, val) + {} + + delayed_delete_vector(size_t n) : m_vec(n) + {} + + template + delayed_delete_vector(InputIt first, InputIt last) : m_vec(first, last) + {} + + iterator begin() noexcept + { + return m_vec.begin() + m_front_offset; + } + + iterator end() noexcept + { + return m_vec.end(); + } + + const_iterator begin() const noexcept + { + return m_vec.begin() + m_front_offset; + } + + const_iterator end() const noexcept + { + return m_vec.end(); + } + + reverse_iterator rbegin() noexcept + { + return m_vec.rbegin(); + } + + const_reverse_iterator rbegin() const noexcept + { + return m_vec.rbegin(); + } + + reverse_iterator rend() noexcept + { + return m_vec.rend() - m_front_offset; + } + + const_reverse_iterator rend() const noexcept + { + return m_vec.rend() - m_front_offset; + } + + reference operator[](size_type pos) + { + return m_vec[pos + m_front_offset]; + } + + const_reference operator[](size_type pos) const + { + return m_vec[pos + m_front_offset]; + } + + reference at(size_type pos) + { + return m_vec.at(pos + m_front_offset); + } + + const_reference at(size_type pos) const + { + return m_vec.at(pos + m_front_offset); + } + + void push_back(const T& value) + { + m_vec.push_back(value); + } + + iterator insert(iterator pos, const T& value) + { + return m_vec.insert(pos, value); + } + + iterator insert(const_iterator pos, T&& value) + { + return m_vec.insert(pos, std::move(value)); + } + + template + void insert(iterator pos, InputIt first, InputIt last) + { + m_vec.insert(pos, first, last); + } + + void resize(size_type count) + { + clear_removed(); + m_vec.resize(count); + } + + iterator erase(iterator pos) + { + if (pos == m_vec.begin() + m_front_offset) + { + ++m_front_offset; + return m_vec.begin() + m_front_offset; + } + else + return m_vec.erase(pos); + } + + iterator erase(iterator first, iterator last) + { + return m_vec.erase(first, last); + } + + size_type capacity() const noexcept + { + return m_vec.capacity(); + } + + void shrink_to_fit() + { + clear_removed(); + m_vec.shrink_to_fit(); + } + + void reserve(size_type new_cap) + { + clear_removed(); + m_vec.reserve(new_cap); + } + + size_type size() const + { + return m_vec.size() - m_front_offset; + } + + template + void assign(InputIt first, InputIt last) + { + clear_removed(); + m_vec.assign(first, last); + } + + T* data() + { + return m_vec.data() + m_front_offset; + } + + const T* data() const + { + return m_vec.data() + m_front_offset; + } + +private: + void clear_removed() + { + m_vec.erase(m_vec.begin(), m_vec.begin() + m_front_offset); + m_front_offset = 0; + } +}; + +namespace detail { + +template<> +struct is_std_vector_bool_store> +{ + using type = std::true_type; +}; + +} // namespace detail + +template +bool operator==(const delayed_delete_vector& lhs, const delayed_delete_vector& rhs) +{ + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} + +template class StoreT> +class element_block : public base_element_block +{ +public: + using store_type = StoreT>; + static constexpr element_t block_type = TypeId; + +protected: + store_type m_array; + + element_block() : base_element_block(TypeId) + {} + element_block(size_t n) : base_element_block(TypeId), m_array(n) + {} + element_block(size_t n, const ValueT& val) : base_element_block(TypeId), m_array(n, val) + {} + + template + element_block(const Iter& it_begin, const Iter& it_end) : base_element_block(TypeId), m_array(it_begin, it_end) + {} + +public: + typedef typename store_type::iterator iterator; + typedef typename store_type::reverse_iterator reverse_iterator; + typedef typename store_type::const_iterator const_iterator; + typedef typename store_type::const_reverse_iterator const_reverse_iterator; + typedef ValueT value_type; + +private: + template + class base_range_type + { + using block_type = mdds::detail::mutable_t; + block_type& m_blk; + + public: + using iter_type = std::conditional_t; + + base_range_type(block_type& blk) : m_blk(blk) + {} + + iter_type begin() + { + return element_block::begin(m_blk); + } + + iter_type end() + { + return element_block::end(m_blk); + } + }; + +public: + using range_type = base_range_type; + using const_range_type = base_range_type; + + bool operator==(const Self& r) const + { + return m_array == r.m_array; + } + + bool operator!=(const Self& r) const + { + return !operator==(r); + } + + static const value_type& at(const base_element_block& block, typename store_type::size_type pos) + { + return get(block).m_array.at(pos); + } + + static value_type& at(base_element_block& block, typename store_type::size_type pos) + { + return get(block).m_array.at(pos); + } + + static value_type* data(base_element_block& block) + { + return get(block).m_array.data(); + } + + static typename store_type::size_type size(const base_element_block& block) + { + return get(block).m_array.size(); + } + + static iterator begin(base_element_block& block) + { + return get(block).m_array.begin(); + } + + static iterator end(base_element_block& block) + { + return get(block).m_array.end(); + } + + static const_iterator begin(const base_element_block& block) + { + return get(block).m_array.begin(); + } + + static const_iterator end(const base_element_block& block) + { + return get(block).m_array.end(); + } + + static const_iterator cbegin(const base_element_block& block) + { + return get(block).m_array.begin(); + } + + static const_iterator cend(const base_element_block& block) + { + return get(block).m_array.end(); + } + + static reverse_iterator rbegin(base_element_block& block) + { + return get(block).m_array.rbegin(); + } + + static reverse_iterator rend(base_element_block& block) + { + return get(block).m_array.rend(); + } + + static const_reverse_iterator rbegin(const base_element_block& block) + { + return get(block).m_array.rbegin(); + } + + static const_reverse_iterator rend(const base_element_block& block) + { + return get(block).m_array.rend(); + } + + static const_reverse_iterator crbegin(const base_element_block& block) + { + return get(block).m_array.rbegin(); + } + + static const_reverse_iterator crend(const base_element_block& block) + { + return get(block).m_array.rend(); + } + + static const_range_type range(const base_element_block& block) + { + return const_range_type(block); + } + + static range_type range(base_element_block& block) + { + return range_type(block); + } + + static Self& get(base_element_block& block) + { +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + if (get_block_type(block) != TypeId) + { + std::ostringstream os; + os << "incorrect block type: expected block type=" << TypeId + << ", passed block type=" << get_block_type(block); + throw general_error(os.str()); + } +#endif + return static_cast(block); + } + + static const Self& get(const base_element_block& block) + { +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + if (get_block_type(block) != TypeId) + { + std::ostringstream os; + os << "incorrect block type: expected block type=" << TypeId + << ", passed block type=" << get_block_type(block); + throw general_error(os.str()); + } +#endif + return static_cast(block); + } + + static void set_value(base_element_block& blk, size_t pos, const ValueT& val) + { + get(blk).m_array[pos] = val; + } + + static void get_value(const base_element_block& blk, size_t pos, ValueT& val) + { + val = get(blk).m_array[pos]; + } + + static value_type get_value(const base_element_block& blk, size_t pos) + { + return get(blk).m_array[pos]; + } + + static void append_value(base_element_block& blk, const ValueT& val) + { + get(blk).m_array.push_back(val); + } + + static void prepend_value(base_element_block& blk, const ValueT& val) + { + store_type& blk2 = get(blk).m_array; + blk2.insert(blk2.begin(), val); + } + + static Self* create_block(size_t init_size) + { + return new Self(init_size); + } + + static void delete_block(const base_element_block* p) + { + delete static_cast(p); + } + + static void resize_block(base_element_block& blk, size_t new_size) + { + store_type& st = get(blk).m_array; + st.resize(new_size); + + // Test if the vector's capacity is larger than twice its current + // size, and if so, shrink its capacity to free up some memory. + if (new_size < (detail::get_block_capacity(st) / 2)) + detail::shrink_to_fit(st); + } + +#ifdef MDDS_UNIT_TEST + static void print_block(const base_element_block& blk) + { + const store_type& blk2 = get(blk).m_array; + for (const auto& val : blk2) + std::cout << val << " "; + + std::cout << std::endl; + } +#else + static void print_block(const base_element_block&) + {} +#endif + + static void erase_value(base_element_block& blk, size_t pos) + { + store_type& blk2 = get(blk).m_array; + blk2.erase(blk2.begin() + pos); + } + + static void erase_values(base_element_block& blk, size_t pos, size_t size) + { + store_type& blk2 = get(blk).m_array; + blk2.erase(blk2.begin() + pos, blk2.begin() + pos + size); + } + + static void append_block(base_element_block& dest, const base_element_block& src) + { + store_type& d = get(dest).m_array; + const store_type& s = get(src).m_array; + d.insert(d.end(), s.begin(), s.end()); + } + + static void append_values_from_block( + base_element_block& dest, const base_element_block& src, size_t begin_pos, size_t len) + { + store_type& d = get(dest).m_array; + const store_type& s = get(src).m_array; + std::pair its = get_iterator_pair(s, begin_pos, len); + detail::reserve(d, d.size() + len); + d.insert(d.end(), its.first, its.second); + } + + static void assign_values_from_block( + base_element_block& dest, const base_element_block& src, size_t begin_pos, size_t len) + { + store_type& d = get(dest).m_array; + const store_type& s = get(src).m_array; + std::pair its = get_iterator_pair(s, begin_pos, len); + d.assign(its.first, its.second); + } + + static void prepend_values_from_block( + base_element_block& dest, const base_element_block& src, size_t begin_pos, size_t len) + { + store_type& d = get(dest).m_array; + const store_type& s = get(src).m_array; + std::pair its = get_iterator_pair(s, begin_pos, len); + detail::reserve(d, d.size() + len); + d.insert(d.begin(), its.first, its.second); + } + + static void swap_values(base_element_block& blk1, base_element_block& blk2, size_t pos1, size_t pos2, size_t len) + { + store_type& st1 = get(blk1).m_array; + store_type& st2 = get(blk2).m_array; + assert(pos1 + len <= st1.size()); + assert(pos2 + len <= st2.size()); + + typename store_type::iterator it1 = st1.begin(), it2 = st2.begin(); + std::advance(it1, pos1); + std::advance(it2, pos2); + + for (size_t i = 0; i < len; ++i, ++it1, ++it2) + { + value_type v1 = *it1, v2 = *it2; + *it1 = v2; + *it2 = v1; + } + } + + static bool equal_block(const base_element_block& left, const base_element_block& right) + { + return get(left) == get(right); + } + + template + static void set_values(base_element_block& block, size_t pos, const Iter& it_begin, const Iter& it_end) + { + store_type& d = get(block).m_array; + typename store_type::iterator it_dest = d.begin(); + std::advance(it_dest, pos); + for (Iter it = it_begin; it != it_end; ++it, ++it_dest) + *it_dest = *it; + } + + template + static void append_values(base_element_block& block, const Iter& it_begin, const Iter& it_end) + { + store_type& d = get(block).m_array; + typename store_type::iterator it = d.end(); + d.insert(it, it_begin, it_end); + } + + template + static void prepend_values(base_element_block& block, const Iter& it_begin, const Iter& it_end) + { + store_type& d = get(block).m_array; + d.insert(d.begin(), it_begin, it_end); + } + + template + static void assign_values(base_element_block& dest, const Iter& it_begin, const Iter& it_end) + { + store_type& d = get(dest).m_array; + d.assign(it_begin, it_end); + } + + template + static void insert_values(base_element_block& block, size_t pos, const Iter& it_begin, const Iter& it_end) + { + store_type& blk = get(block).m_array; + blk.insert(blk.begin() + pos, it_begin, it_end); + } + + static size_t capacity(const base_element_block& block) + { + const store_type& blk = get(block).m_array; + return detail::get_block_capacity(blk); + } + + static void reserve(base_element_block& block, std::size_t size) + { + store_type& blk = get(block).m_array; + detail::reserve(blk, size); + } + + static void shrink_to_fit(base_element_block& block) + { + store_type& blk = get(block).m_array; + detail::shrink_to_fit(blk); + } + +private: + static std::pair get_iterator_pair( + const store_type& array, size_t begin_pos, size_t len) + { + assert(begin_pos + len <= array.size()); + const_iterator it = array.begin(); + std::advance(it, begin_pos); + const_iterator it_end = it; + std::advance(it_end, len); + return std::pair(it, it_end); + } +}; + +template class StoreT> +class copyable_element_block : public element_block +{ + using base_type = element_block; + +protected: + copyable_element_block() : base_type() + {} + copyable_element_block(size_t n) : base_type(n) + {} + copyable_element_block(size_t n, const ValueT& val) : base_type(n, val) + {} + + template + copyable_element_block(const Iter& it_begin, const Iter& it_end) : base_type(it_begin, it_end) + {} + +public: + using base_type::get; + + static Self* clone_block(const base_element_block& blk) + { + // Use copy constructor to copy the data. + return new Self(get(blk)); + } +}; + +template class StoreT> +class noncopyable_element_block : public element_block +{ + using base_type = element_block; + +protected: + noncopyable_element_block() : base_type() + {} + noncopyable_element_block(size_t n) : base_type(n) + {} + noncopyable_element_block(size_t n, const ValueT& val) : base_type(n, val) + {} + + template + noncopyable_element_block(const Iter& it_begin, const Iter& it_end) : base_type(it_begin, it_end) + {} + +public: + noncopyable_element_block(const noncopyable_element_block&) = delete; + noncopyable_element_block& operator=(const noncopyable_element_block&) = delete; + + static Self* clone_block(const base_element_block&) + { + throw element_block_error("attempted to clone a noncopyable element block."); + } +}; + +/** + * Get the numerical block type ID from a given element block instance. + * + * @param blk element block instance + * + * @return numerical value representing the ID of a element block. + */ +inline element_t get_block_type(const base_element_block& blk) +{ + return blk.type; +} + +/** + * Template for default, unmanaged element block for use in + * multi_type_vector. + */ +template class StoreT = delayed_delete_vector> +struct default_element_block + : public copyable_element_block, TypeId, ValueT, StoreT> +{ + using self_type = default_element_block; + using base_type = copyable_element_block; + + default_element_block() : base_type() + {} + default_element_block(size_t n) : base_type(n) + {} + default_element_block(size_t n, const ValueT& val) : base_type(n, val) + {} + + template + default_element_block(const Iter& it_begin, const Iter& it_end) : base_type(it_begin, it_end) + {} + + static self_type* create_block_with_value(size_t init_size, const ValueT& val) + { + return new self_type(init_size, val); + } + + template + static self_type* create_block_with_values(const Iter& it_begin, const Iter& it_end) + { + return new self_type(it_begin, it_end); + } + + static void overwrite_values(base_element_block&, size_t, size_t) + { + // Do nothing. + } +}; + +/** + * Template for element block that stores pointers to objects whose life + * cycles are managed by the block. + */ +template class StoreT = delayed_delete_vector> +struct managed_element_block + : public copyable_element_block, TypeId, ValueT*, StoreT> +{ + using self_type = managed_element_block; + using base_type = copyable_element_block; + + using base_type::get; + using base_type::m_array; + using base_type::reserve; + using base_type::set_value; + + managed_element_block() : base_type() + {} + managed_element_block(size_t n) : base_type(n) + {} + managed_element_block(const managed_element_block& r) + { + detail::reserve(m_array, r.m_array.size()); + for (const auto& v : r.m_array) + m_array.push_back(new ValueT(*v)); + } + + template + managed_element_block(const Iter& it_begin, const Iter& it_end) : base_type(it_begin, it_end) + {} + + ~managed_element_block() + { + std::for_each(m_array.begin(), m_array.end(), std::default_delete()); + } + + static self_type* create_block_with_value(size_t init_size, ValueT* val) + { + // Managed blocks don't support initialization with value. + if (init_size > 1) + throw general_error("You can't create a managed block with initial value."); + + std::unique_ptr blk = std::make_unique(init_size); + if (init_size == 1) + set_value(*blk, 0, val); + + return blk.release(); + } + + template + static self_type* create_block_with_values(const Iter& it_begin, const Iter& it_end) + { + return new self_type(it_begin, it_end); + } + + static void overwrite_values(base_element_block& block, size_t pos, size_t len) + { + managed_element_block& blk = get(block); + typename managed_element_block::store_type::iterator it = blk.m_array.begin() + pos; + typename managed_element_block::store_type::iterator it_end = it + len; + std::for_each(it, it_end, std::default_delete()); + } +}; + +template class StoreT = delayed_delete_vector> +struct noncopyable_managed_element_block + : public noncopyable_element_block< + noncopyable_managed_element_block, TypeId, ValueT*, StoreT> +{ + using self_type = noncopyable_managed_element_block; + using base_type = noncopyable_element_block; + + using base_type::get; + using base_type::m_array; + using base_type::set_value; + + noncopyable_managed_element_block() : base_type() + {} + noncopyable_managed_element_block(size_t n) : base_type(n) + {} + + template + noncopyable_managed_element_block(const Iter& it_begin, const Iter& it_end) : base_type(it_begin, it_end) + {} + + ~noncopyable_managed_element_block() + { + std::for_each(m_array.begin(), m_array.end(), std::default_delete()); + } + + static self_type* create_block_with_value(size_t init_size, ValueT* val) + { + // Managed blocks don't support initialization with value. + if (init_size > 1) + throw general_error("You can't create a managed block with initial value."); + + std::unique_ptr blk = std::make_unique(init_size); + if (init_size == 1) + set_value(*blk, 0, val); + + return blk.release(); + } + + template + static self_type* create_block_with_values(const Iter& it_begin, const Iter& it_end) + { + return new self_type(it_begin, it_end); + } + + static void overwrite_values(base_element_block& block, size_t pos, size_t len) + { + noncopyable_managed_element_block& blk = get(block); + typename noncopyable_managed_element_block::store_type::iterator it = blk.m_array.begin() + pos; + typename noncopyable_managed_element_block::store_type::iterator it_end = it + len; + std::for_each(it, it_end, std::default_delete()); + } +}; + +namespace detail { + +template +bool get_block_element_at(const mdds::mtv::base_element_block& data, size_t offset, std::true_type) +{ + auto it = Blk::cbegin(data); + std::advance(it, offset); + return *it; +} + +template +typename Blk::value_type get_block_element_at(const mdds::mtv::base_element_block& data, size_t offset, std::false_type) +{ + return Blk::at(data, offset); +} + +template +typename Blk::value_type get_block_element_at(const mdds::mtv::base_element_block& data, size_t offset) +{ + typename mdds::mtv::detail::has_std_vector_bool_store::type v; + return get_block_element_at(data, offset, v); +} + +} // namespace detail + +}} // namespace mdds::mtv + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/types_util.hpp b/include/mdds/multi_type_vector/types_util.hpp new file mode 100644 index 0000000..f4f3330 --- /dev/null +++ b/include/mdds/multi_type_vector/types_util.hpp @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2022 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#pragma once + +#include + +namespace mdds { namespace mtv { namespace detail { + +template +struct has_capacity_method +{ + using yes_type = char; + using no_type = int; + + template + struct test_has_method + { + }; + + template + static yes_type test(test_has_method*); + template + static no_type test(...); + + using type = std::conditional_t(0)) == sizeof(yes_type), std::true_type, std::false_type>; +}; + +template +std::size_t get_block_capacity(const T& blk, std::true_type) +{ + return blk.capacity(); +} + +template +std::size_t get_block_capacity(const T&, std::false_type) +{ + return 0; +} + +template +std::size_t get_block_capacity(const T& blk) +{ + typename has_capacity_method::type v; + return get_block_capacity(blk, v); +} + +template +struct has_reserve_method +{ + using yes_type = char; + using no_type = int; + + template + struct test_has_method + { + }; + + template + static yes_type test(test_has_method*); + template + static no_type test(...); + + using type = std::conditional_t(0)) == sizeof(yes_type), std::true_type, std::false_type>; +}; + +template +void reserve(T& blk, typename T::size_type size, std::true_type) +{ + return blk.reserve(size); +} + +template +void reserve(T&, typename T::size_type, std::false_type) +{} + +template +void reserve(T& blk, typename T::size_type size) +{ + typename has_reserve_method::type v; + reserve(blk, size, v); +} + +template +struct has_shrink_to_fit_method +{ + using yes_type = char; + using no_type = int; + + template + struct test_has_method + { + }; + + template + static yes_type test(test_has_method*); + template + static no_type test(...); + + using type = std::conditional_t(0)) == sizeof(yes_type), std::true_type, std::false_type>; +}; + +template +void shrink_to_fit(T& blk, std::true_type) +{ + return blk.shrink_to_fit(); +} + +template +void shrink_to_fit(T&, std::false_type) +{} + +template +void shrink_to_fit(T& blk) +{ + typename has_shrink_to_fit_method::type v; + shrink_to_fit(blk, v); +} + +template +struct is_std_vector_bool_store +{ + using type = std::false_type; +}; + +template<> +struct is_std_vector_bool_store> +{ + using type = std::true_type; +}; + +template +struct has_std_vector_bool_store +{ + using type = typename is_std_vector_bool_store::type; +}; + +}}} // namespace mdds::mtv::detail + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector/util.hpp b/include/mdds/multi_type_vector/util.hpp new file mode 100644 index 0000000..929eea1 --- /dev/null +++ b/include/mdds/multi_type_vector/util.hpp @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_UTIL_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_DIR_UTIL_HPP + +#include "./block_funcs.hpp" + +#include + +namespace mdds { namespace mtv { + +/** + * Empty event function handler structure, used when no custom function + * handler is specified. + */ +struct empty_event_func +{ + /** + * Callback function for element block acquisition events. This gets called + * whenever the container acquires a new element block either as a result of + * a new element block creation or a transfer of an existing element block + * from another container. + * + * @param block pointer to the acquired element block instance. + */ + void element_block_acquired(const base_element_block* block) + { + (void)block; + } + + /** + * Callback function for element block release events. This gets called + * whenever the container releases an existing element block either because + * the block is about to be deleted or to be transferred to another + * container. + * + * @param block pointer to the element block instance being released. + */ + void element_block_released(const base_element_block* block) + { + (void)block; + } +}; + +/** + * Default trait to be used when no custom trait is specified. + */ +struct default_traits +{ + /** + * Class or struct type that contains callback functions for element block + * events as its member functions. + */ + using event_func = empty_event_func; + + /** + * Static value specifying the loop-unrolling factor to use for the block + * position adjustment function. This must be a const expression. + */ + static constexpr lu_factor_t loop_unrolling = lu_factor_t::lu16; + + /** + * Type that contains block functions used throughout the multi_type_vector + * implementation. The user must overwrite this type to specify one or more + * block types as template arguments to element_block_funcs. Alternatively, + * you may be interested in using standard_element_blocks_traits which + * already supports the pre-defined block types for the optional standard + * data types. + */ + using block_funcs = element_block_funcs<>; +}; + +namespace detail { + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + +template +struct has_trace : std::false_type +{ +}; + +template +struct has_trace : std::true_type +{ +}; + +template +struct call_trace +{ + int& call_depth; + + call_trace(int& _call_depth) : call_depth(_call_depth) + { + ++call_depth; + } + ~call_trace() noexcept + { + --call_depth; + } + + void call(std::false_type, const ::mdds::mtv::trace_method_properties_t&) const + { + // sink + } + + void call(std::true_type, const ::mdds::mtv::trace_method_properties_t& props) const + { + // In case of recursive calls, only trace the first encountered method. + if (call_depth <= 1) + Traits::trace(props); + } + + void operator()(const ::mdds::mtv::trace_method_properties_t& props) const + { + call(has_trace{}, props); + } +}; + +#endif + +inline void throw_block_position_not_found( + const char* method_sig, int line, size_t pos, size_t block_size, size_t container_size) +{ + std::ostringstream os; + os << method_sig << "#" << line << ": block position not found! (logical pos=" << pos + << ", block size=" << block_size << ", logical size=" << container_size << ")"; + throw std::out_of_range(os.str()); +} + +/** + * Given a pair of iterators comprising the input value sequence, and a + * desired logical insertion position, calculate the position of the last + * input value. Also check if the input value sequence is empty. + * + * @exception std::out_of_range if the end position is greater than the last + * allowed position of the destination storage. + * + * @param it_begin iterator pointing to the first element of the input value + * sequence. + * @param it_end iterator point to the position past the last element of the + * input value sequence. + * @param pos desired insertion position. + * @param total_size total logical size of the destination storage. + * + * @return position of the last input value (first) and a flag on whether or + * not the input value sequence is empty (second). + */ +template +std::pair<_SizeT, bool> calc_input_end_position(const _T& it_begin, const _T& it_end, _SizeT pos, _SizeT total_size) +{ + using ret_type = std::pair<_SizeT, bool>; + + _SizeT length = std::distance(it_begin, it_end); + if (!length) + // empty data array. nothing to do. + return ret_type(0, false); + + _SizeT end_pos = pos + length - 1; + if (end_pos >= total_size) + throw std::out_of_range("Input data sequence is too long."); + + return ret_type(end_pos, true); +} + +template +T advance_position(const T& pos, int steps) +{ + T ret = pos; + + if (steps > 0) + { + while (steps > 0) + { + if (ret.second + steps < ret.first->size) + { + // element is still in the same block. + ret.second += steps; + break; + } + else + { + steps -= static_cast(ret.first->size - ret.second); + ++ret.first; + ret.second = 0; + } + } + } + else + { + while (steps < 0) + { + if (static_cast(ret.second) >= -steps) + { + ret.second += steps; + break; + } + else + { + steps += static_cast(ret.second + 1); + --ret.first; + ret.second = ret.first->size - 1; + } + } + } + + return ret; +} + +} // namespace detail + +}} // namespace mdds::mtv + +#ifdef MDDS_MULTI_TYPE_VECTOR_DEBUG + +#define MDDS_MTV_TRACE(method_type) \ + ::mdds::mtv::detail::call_trace mdds_mtv_ct(m_trace_call_depth); \ + mdds_mtv_ct({trace_method_t::method_type, this, __func__, "", __FILE__, __LINE__}) + +#define MDDS_MTV_TRACE_ARGS(method_type, stream) \ + ::mdds::mtv::detail::call_trace mdds_mtv_ct(m_trace_call_depth); \ + do \ + { \ + std::ostringstream _os_; \ + _os_ << stream; \ + mdds_mtv_ct({trace_method_t::method_type, this, __func__, _os_.str(), __FILE__, __LINE__}); \ + } while (false) + +#else + +#define MDDS_MTV_TRACE(...) + +#define MDDS_MTV_TRACE_ARGS(...) + +#endif + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector_itr.hpp b/include/mdds/multi_type_vector_itr.hpp new file mode 100644 index 0000000..d261e3d --- /dev/null +++ b/include/mdds/multi_type_vector_itr.hpp @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_ITR_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_ITR_HPP + +#include "./multi_type_vector/aos/iterator.hpp" + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/multi_type_vector_macro.hpp b/include/mdds/multi_type_vector_macro.hpp new file mode 100644 index 0000000..be7e697 --- /dev/null +++ b/include/mdds/multi_type_vector_macro.hpp @@ -0,0 +1,33 @@ +/************************************************************************* + * + * Copyright (c) 2012-2021 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_MACRO_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_MACRO_HPP + +#include "./multi_type_vector/macro.hpp" + +#endif diff --git a/include/mdds/multi_type_vector_types.hpp b/include/mdds/multi_type_vector_types.hpp new file mode 100644 index 0000000..3339558 --- /dev/null +++ b/include/mdds/multi_type_vector_types.hpp @@ -0,0 +1,33 @@ +/************************************************************************* + * + * Copyright (c) 2012-2018 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_MULTI_TYPE_VECTOR_TYPES_HPP +#define INCLUDED_MDDS_MULTI_TYPE_VECTOR_TYPES_HPP + +#include "./multi_type_vector/types.hpp" + +#endif diff --git a/include/mdds/node.hpp b/include/mdds/node.hpp new file mode 100644 index 0000000..e94fcda --- /dev/null +++ b/include/mdds/node.hpp @@ -0,0 +1,490 @@ +/************************************************************************* + * + * Copyright (c) 2008-2014 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef __MDDS_NODE_HXX__ +#define __MDDS_NODE_HXX__ + +#include +#include +#include + +#include + +namespace mdds { namespace __st { + +#ifdef MDDS_DEBUG_NODE_BASE +size_t node_instance_count = 0; +#endif + +struct node_base +{ + node_base* parent; /// parent nonleaf_node + bool is_leaf; + + node_base(bool _is_leaf) : parent(nullptr), is_leaf(_is_leaf) + {} + node_base(const node_base& r) : parent(nullptr), is_leaf(r.is_leaf) + {} +}; + +template +struct nonleaf_node : public node_base +{ + typedef typename T::nonleaf_value_type nonleaf_value_type; + typedef typename T::fill_nonleaf_value_handler fill_nonleaf_value_handler; + typedef typename T::init_handler init_handler; + typedef typename T::dispose_handler dispose_handler; +#ifdef MDDS_UNIT_TEST + typedef typename T::to_string_handler to_string_handler; +#endif + nonleaf_value_type value_nonleaf; + + node_base* left; /// left child nonleaf_node + node_base* right; /// right child nonleaf_node + +private: + fill_nonleaf_value_handler _hdl_fill_nonleaf; + init_handler _hdl_init; + dispose_handler _hdl_dispose; +#ifdef MDDS_UNIT_TEST + to_string_handler _hdl_to_string; +#endif + +public: + nonleaf_node() : node_base(false), left(nullptr), right(nullptr) + { + _hdl_init(*this); + } + + /** + * When copying nonleaf_node, only the stored values should be copied. + * Connections to the parent, left and right nodes must not be copied. + */ + nonleaf_node(const nonleaf_node& r) : node_base(r), left(nullptr), right(nullptr) + { + value_nonleaf = r.value_nonleaf; + } + + /** + * Like the copy constructor, only the stored values should be copied. + */ + nonleaf_node& operator=(const nonleaf_node& r) + { + if (this == &r) + // assignment to self. + return *this; + + value_nonleaf = r.value_nonleaf; + return *this; + } + + ~nonleaf_node() + { + dispose(); + } + + void dispose() + { + _hdl_dispose(*this); + } + + bool equals(const nonleaf_node& r) const + { + return value_nonleaf == r.value_nonleaf; + } + + void fill_nonleaf_value(const node_base* left_node, const node_base* right_node) + { + _hdl_fill_nonleaf(*this, left_node, right_node); + } + +#ifdef MDDS_UNIT_TEST + void dump_value() const + { + ::std::cout << _hdl_to_string(*this); + } + + ::std::string to_string() const + { + return _hdl_to_string(*this); + } +#endif +}; + +template +struct node : public node_base +{ + typedef ::boost::intrusive_ptr node_ptr; + + typedef typename T::leaf_value_type leaf_value_type; + typedef typename T::init_handler init_handler; + typedef typename T::dispose_handler dispose_handler; +#ifdef MDDS_UNIT_TEST + typedef typename T::to_string_handler to_string_handler; +#endif + + static size_t get_instance_count() + { +#ifdef MDDS_DEBUG_NODE_BASE + return node_instance_count; +#else + return 0; +#endif + } + + leaf_value_type value_leaf; + + node_ptr prev; /// previous sibling leaf node. + node_ptr next; /// next sibling leaf node. + + size_t refcount; + +private: + init_handler _hdl_init; + dispose_handler _hdl_dispose; +#ifdef MDDS_UNIT_TEST + to_string_handler _hdl_to_string; +#endif + +public: + node() : node_base(true), refcount(0) + { +#ifdef MDDS_DEBUG_NODE_BASE + ++node_instance_count; +#endif + _hdl_init(*this); + } + + /** + * When copying node, only the stored values should be copied. + * Connections to the parent, left and right nodes must not be copied. + */ + node(const node& r) : node_base(r), refcount(0) + { +#ifdef MDDS_DEBUG_NODE_BASE + ++node_instance_count; +#endif + value_leaf = r.value_leaf; + } + + /** + * Like the copy constructor, only the stored values should be copied. + */ + node& operator=(const node& r) + { + if (this == &r) + // assignment to self. + return *this; + + value_leaf = r.value_leaf; + return *this; + } + + ~node() + { +#ifdef MDDS_DEBUG_NODE_BASE + --node_instance_count; +#endif + dispose(); + } + + void dispose() + { + _hdl_dispose(*this); + } + + bool equals(const node& r) const + { + return value_leaf == r.value_leaf; + } + +#ifdef MDDS_UNIT_TEST + void dump_value() const + { + ::std::cout << _hdl_to_string(*this); + } + + ::std::string to_string() const + { + return _hdl_to_string(*this); + } +#endif +}; + +template +inline void intrusive_ptr_add_ref(node* p) +{ + ++p->refcount; +} + +template +inline void intrusive_ptr_release(node* p) +{ + --p->refcount; + if (!p->refcount) + delete p; +} + +template +void link_nodes(typename node::node_ptr& left, typename node::node_ptr& right) +{ + left->next = right; + right->prev = left; +} + +template +class tree_builder +{ +public: + typedef mdds::__st::node leaf_node; + typedef typename mdds::__st::node::node_ptr leaf_node_ptr; + typedef mdds::__st::nonleaf_node nonleaf_node; + typedef std::vector nonleaf_node_pool_type; + + tree_builder(nonleaf_node_pool_type& pool) : m_pool(pool), m_pool_pos(pool.begin()), m_pool_pos_end(pool.end()) + {} + + nonleaf_node* build(const leaf_node_ptr& left_leaf_node) + { + if (!left_leaf_node) + // The left leaf node is empty. Nothing to build. + return nullptr; + + leaf_node_ptr node1 = left_leaf_node; + + std::vector node_list; + while (true) + { + leaf_node_ptr node2 = node1->next; + nonleaf_node* parent_node = make_parent_node(node1.get(), node2.get()); + node_list.push_back(parent_node); + + if (!node2 || !node2->next) + // no more nodes. Break out of the loop. + break; + + node1 = node2->next; + } + + return build_tree_non_leaf(node_list); + } + +private: + nonleaf_node* make_parent_node(node_base* node1, node_base* node2) + { + assert(m_pool_pos != m_pool_pos_end); + + nonleaf_node* parent_node = &(*m_pool_pos); + ++m_pool_pos; + node1->parent = parent_node; + parent_node->left = node1; + if (node2) + { + node2->parent = parent_node; + parent_node->right = node2; + } + + parent_node->fill_nonleaf_value(node1, node2); + return parent_node; + } + + nonleaf_node* build_tree_non_leaf(const std::vector& node_list) + { + size_t node_count = node_list.size(); + if (node_count == 1) + { + return node_list.front(); + } + else if (node_count == 0) + return nullptr; + + std::vector new_node_list; + nonleaf_node* node1 = nullptr; + typename std::vector::const_iterator it = node_list.begin(); + typename std::vector::const_iterator it_end = node_list.end(); + for (bool even_itr = false; it != it_end; ++it, even_itr = !even_itr) + { + if (even_itr) + { + nonleaf_node* node2 = *it; + nonleaf_node* parent_node = make_parent_node(node1, node2); + new_node_list.push_back(parent_node); + node1 = nullptr; + node2 = nullptr; + } + else + node1 = *it; + } + + if (node1) + { + // Un-paired node still needs a parent... + nonleaf_node* parent_node = make_parent_node(node1, nullptr); + new_node_list.push_back(parent_node); + } + + // Move up one level, and do the same procedure until the root node is reached. + return build_tree_non_leaf(new_node_list); + } + + nonleaf_node_pool_type& m_pool; + typename nonleaf_node_pool_type::iterator m_pool_pos; + typename nonleaf_node_pool_type::iterator m_pool_pos_end; +}; + +template +void disconnect_all_nodes(node* p) +{ + if (!p) + return; + + p->prev.reset(); + p->next.reset(); + p->parent = nullptr; +} + +template +void disconnect_leaf_nodes(node* left_node, node* right_node) +{ + if (!left_node || !right_node) + return; + + // Go through all leaf nodes, and disconnect their links. + node* cur_node = left_node; + do + { + node* next_node = cur_node->next.get(); + disconnect_all_nodes(cur_node); + cur_node = next_node; + } while (cur_node != right_node); + + disconnect_all_nodes(right_node); +} + +template +size_t count_leaf_nodes(const node* left_end, const node* right_end) +{ + size_t leaf_count = 1; + const node* p = left_end; + const node* p_end = right_end; + for (; p != p_end; p = p->next.get(), ++leaf_count) + ; + + return leaf_count; +} + +inline size_t count_needed_nonleaf_nodes(size_t leaf_count) +{ + size_t nonleaf_count = 0; + while (true) + { + if (leaf_count == 1) + break; + + if ((leaf_count % 2) == 1) + // Add one to make it an even number. + ++leaf_count; + + leaf_count /= 2; + nonleaf_count += leaf_count; + } + + return nonleaf_count; +} + +#ifdef MDDS_UNIT_TEST + +template +class tree_dumper +{ + typedef std::vector node_list_type; + +public: + static size_t dump(const node_base* root_node) + { + if (!root_node) + return 0; + + node_list_type node_list; + node_list.push_back(root_node); + return dump_layer(node_list, 0); + } + +private: + static size_t dump_layer(const node_list_type& node_list, unsigned int level) + { + using ::std::cout; + using ::std::endl; + + if (node_list.empty()) + return 0; + + size_t node_count = node_list.size(); + + bool is_leaf = node_list.front()->is_leaf; + cout << "level " << level << " (" << (is_leaf ? "leaf" : "non-leaf") << ")" << endl; + + node_list_type new_list; + typename node_list_type::const_iterator it = node_list.begin(), it_end = node_list.end(); + for (; it != it_end; ++it) + { + const node_base* p = *it; + if (!p) + { + cout << "(x) "; + continue; + } + + if (p->is_leaf) + static_cast(p)->dump_value(); + else + static_cast(p)->dump_value(); + + if (p->is_leaf) + continue; + + if (static_cast(p)->left) + { + new_list.push_back(static_cast(p)->left); + if (static_cast(p)->right) + new_list.push_back(static_cast(p)->right); + } + } + cout << endl; + + if (!new_list.empty()) + node_count += dump_layer(new_list, level + 1); + + return node_count; + } +}; + +#endif + +}} // namespace mdds::__st + +#endif diff --git a/include/mdds/point_quad_tree.hpp b/include/mdds/point_quad_tree.hpp new file mode 100644 index 0000000..01c930d --- /dev/null +++ b/include/mdds/point_quad_tree.hpp @@ -0,0 +1,1576 @@ +/************************************************************************* + * + * Copyright (c) 2010-2015 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_POINT_QUAD_TREE_HPP +#define INCLUDED_MDDS_POINT_QUAD_TREE_HPP + +#include "mdds/quad_node.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_POINT_QUAD_TREE 0 + +namespace mdds { + +template +void ensure_order(_PairType& v) +{ + if (v.first > v.second) + ::std::swap(v.first, v.second); +} + +template +void search_region_node(const _NodeType* p, _Key x1, _Key y1, _Key x2, _Key y2, _Inserter& result) +{ + using namespace std; + + if (!p) + return; + + search_region_space_t region = ::mdds::get_search_region_space(p, x1, y1, x2, y2); + + switch (region) + { + case region_center: + result(p); + search_region_node(p->northeast.get(), x1, y1, x2, y2, result); + search_region_node(p->northwest.get(), x1, y1, x2, y2, result); + search_region_node(p->southeast.get(), x1, y1, x2, y2, result); + search_region_node(p->southwest.get(), x1, y1, x2, y2, result); + break; + case region_east: + search_region_node(p->northwest.get(), x1, y1, x2, y2, result); + search_region_node(p->southwest.get(), x1, y1, x2, y2, result); + break; + case region_north: + search_region_node(p->southeast.get(), x1, y1, x2, y2, result); + search_region_node(p->southwest.get(), x1, y1, x2, y2, result); + break; + case region_northeast: + search_region_node(p->southwest.get(), x1, y1, x2, y2, result); + break; + case region_northwest: + search_region_node(p->southeast.get(), x1, y1, x2, y2, result); + break; + case region_south: + search_region_node(p->northeast.get(), x1, y1, x2, y2, result); + search_region_node(p->northwest.get(), x1, y1, x2, y2, result); + break; + case region_southeast: + search_region_node(p->northwest.get(), x1, y1, x2, y2, result); + break; + case region_southwest: + search_region_node(p->northeast.get(), x1, y1, x2, y2, result); + break; + case region_west: + search_region_node(p->northeast.get(), x1, y1, x2, y2, result); + search_region_node(p->southeast.get(), x1, y1, x2, y2, result); + break; + default: + throw general_error("unknown search region"); + } +} + +template +class point_quad_tree +{ +private: + class search_result_inserter; + +public: + typedef _Key key_type; + typedef _Value value_type; + typedef size_t size_type; + typedef ::std::vector data_array_type; + + class data_not_found : public ::std::exception + { + }; + +private: + struct node; + typedef ::boost::intrusive_ptr node_ptr; + + struct node : quad_node_base + { + value_type data; + node(key_type _x, key_type _y, value_type _data) : quad_node_base(_x, _y), data(_data) + {} + + node(const node& r) : quad_node_base(r), data(r.data) + {} + + void dispose() + {} + + bool operator==(const node& r) const + { + return quad_node_base::operator==(r) && data == r.data; + } + + node& operator=(const node& r) + { + quad_node_base::operator=(r); + data = r.data; + return *this; + } + }; + + typedef ::std::vector reinsert_tree_array_type; + typedef ::std::pair key_range_type; + +public: + /** + * Node wrapper to allow read-only access to the internal quad node + * structure. + */ + class node_access + { + friend class point_quad_tree<_Key, _Value>; + + public: + node_access northeast() const + { + return node_access(mp->northeast.get()); + } + node_access northwest() const + { + return node_access(mp->northwest.get()); + } + node_access southeast() const + { + return node_access(mp->southeast.get()); + } + node_access southwest() const + { + return node_access(mp->southwest.get()); + } + + value_type data() const + { + return mp->data; + } + key_type x() const + { + return mp->x; + } + key_type y() const + { + return mp->y; + } + + operator bool() const + { + return mp != nullptr; + } + bool operator==(const node_access& r) const + { + return mp == r.mp; + } + + node_access() : mp(nullptr) + {} + ~node_access() + {} + + private: + node_access(const node* p) : mp(p) + {} + + private: + const node* mp; + }; + + struct point + { + key_type x; + key_type y; + point(key_type _x, key_type _y) : x(_x), y(_y) + {} + point() : x(0), y(0) + {} + }; + + class search_results + { + friend class search_result_inserter; + + typedef std::vector res_nodes_type; + typedef std::shared_ptr res_nodes_ptr; + + public: + class const_iterator + { + friend class point_quad_tree<_Key, _Value>::search_results; + typedef typename point_quad_tree<_Key, _Value>::point point; + typedef typename point_quad_tree<_Key, _Value>::value_type parent_value_type; + + public: + // Iterator traits + typedef std::pair value_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef ptrdiff_t difference_type; + typedef ::std::bidirectional_iterator_tag iterator_category; + + const_iterator(res_nodes_ptr& ptr) : mp_res_nodes(ptr), m_end_pos(false) + {} + + const_iterator(const const_iterator& r) + : mp_res_nodes(r.mp_res_nodes), m_cur_pos(r.m_cur_pos), m_cur_value(r.m_cur_value), + m_end_pos(r.m_end_pos) + {} + + const_iterator& operator=(const const_iterator& r) + { + mp_res_nodes = r.mp_res_nodes; + m_cur_pos = r.m_cur_pos; + m_cur_value = r.m_cur_value; + m_end_pos = r.m_end_pos; + return *this; + } + + bool operator==(const const_iterator& r) const + { + if (mp_res_nodes) + { + // Non-empty result set. + return mp_res_nodes.get() == r.mp_res_nodes.get() && m_cur_pos == r.m_cur_pos && + m_end_pos == r.m_end_pos; + } + + // Empty result set. + if (r.mp_res_nodes) + return false; + + return m_end_pos == r.m_end_pos; + } + + bool operator!=(const const_iterator& r) const + { + return !operator==(r); + } + + const value_type& operator*() const + { + return m_cur_value; + } + + const value_type* operator->() const + { + return get_current_value(); + } + + const value_type* operator++() + { + // The only difference between the last data position and the + // end iterator position must be the value of m_end_pos; + // m_cur_pos needs to point to the last data position even + // when the iterator is at the end-of-iterator position. + + typename res_nodes_type::const_iterator cur_pos = m_cur_pos; + if (++cur_pos == mp_res_nodes->end()) + { + m_end_pos = true; + return nullptr; + } + m_cur_pos = cur_pos; + update_current_value(); + return operator->(); + } + + const value_type* operator--() + { + if (m_end_pos) + { + m_end_pos = false; + return get_current_value(); + } + --m_cur_pos; + update_current_value(); + return get_current_value(); + } + + private: + void move_to_front() + { + if (!mp_res_nodes) + { + // Empty data set. + m_end_pos = true; + return; + } + + m_cur_pos = mp_res_nodes->begin(); + m_end_pos = false; + update_current_value(); + } + + void move_to_end() + { + m_end_pos = true; + if (!mp_res_nodes) + // Empty data set. + return; + + m_cur_pos = mp_res_nodes->end(); + --m_cur_pos; // Keep the position at the last data position. + } + + void update_current_value() + { + const node* p = *m_cur_pos; + m_cur_value.first = point(p->x, p->y); + m_cur_value.second = p->data; + } + + const value_type* get_current_value() const + { + return &m_cur_value; + } + + private: + res_nodes_ptr mp_res_nodes; + typename res_nodes_type::const_iterator m_cur_pos; + value_type m_cur_value; + bool m_end_pos : 1; + }; + + search_results() : mp_res_nodes(static_cast(nullptr)) + {} + search_results(const search_results& r) : mp_res_nodes(r.mp_res_nodes) + {} + + typename search_results::const_iterator begin() + { + typename search_results::const_iterator itr(mp_res_nodes); + itr.move_to_front(); + return itr; + } + + typename search_results::const_iterator end() + { + typename search_results::const_iterator itr(mp_res_nodes); + itr.move_to_end(); + return itr; + } + + private: + void push_back(const node* p) + { + if (!mp_res_nodes) + mp_res_nodes.reset(new res_nodes_type); + mp_res_nodes->push_back(p); + } + + private: + res_nodes_ptr mp_res_nodes; + }; + + point_quad_tree(); + point_quad_tree(const point_quad_tree& r); + ~point_quad_tree(); + + /** + * Insert a new data at specified coordinates. It overwrites existing + * data in case one exists at the specified coordinates. + * + * @param x x coordinate of new data position + * @param y y coordinate of new data position + * @param data data being inserted at the specified coordinates. + */ + void insert(key_type x, key_type y, value_type data); + + /** + * Perform region search (aka window search), that is, find all points + * that fall within specified rectangular region. The boundaries are + * inclusive. + * + * @param x1 left coordinate of the search region + * @param y1 top coordinate of the search region + * @param x2 right coordinate of the search region + * @param y2 bottom coordinate of the search region + * @param result this array will contain all data found without specified + * region. + */ + void search_region(key_type x1, key_type y1, key_type x2, key_type y2, data_array_type& result) const; + + /** + * Perform region search (aka window search), that is, find all points + * that fall within specified rectangular region. The boundaries are + * inclusive. + * + * @param x1 left coordinate of the search region + * @param y1 top coordinate of the search region + * @param x2 right coordinate of the search region + * @param y2 bottom coordinate of the search region + * + * @return search result object containing all data found within the + * specified region. + */ + search_results search_region(key_type x1, key_type y1, key_type x2, key_type y2) const; + + /** + * Find data at specified coordinates. If no data exists at the specified + * coordinates, this method throws a + * point_quad_tree::data_not_found exception. + * + * @param x x coordinate + * @param y y coordinate + * + * @return data found at the specified coordinates. + */ + value_type find(key_type x, key_type y) const; + + /** + * Remove data from specified coordinates. This method does nothing if no + * data exists at the specified coordinates. + * + * @param x x coordinate + * @param y y coordinate + */ + void remove(key_type x, key_type y); + + /** + * Swap the internal state with another instance. + * + * @param r another instance to swap internals with. + */ + void swap(point_quad_tree& r); + + /** + * Remove all stored data. + */ + void clear(); + + /** + * Check whether or not the container is empty. + * + * @return bool true if empty, false otherwise. + */ + bool empty() const; + + /** + * Get the number of stored data. + * + * @return the number of data currently stored in the container. + */ + size_t size() const; + + /** + * Get read-only access to the internal quad node tree. + * + * @return root node + */ + node_access get_node_access() const; + + point_quad_tree& operator=(const point_quad_tree& r); + + bool operator==(const point_quad_tree& r) const; + + bool operator!=(const point_quad_tree& r) const + { + return !operator==(r); + } + +#ifdef MDDS_UNIT_TEST +public: +#else +private: +#endif + /** + * Data stored in each node. Used for verification of data stored in tree + * during unit testing. + */ + struct node_data + { + key_type x; + key_type y; + value_type data; + node_data(key_type _x, key_type _y, value_type _data) : x(_x), y(_y), data(_data) + {} + node_data(const node_data& r) : x(r.x), y(r.y), data(r.data) + {} + + bool operator==(const node_data& r) const + { + return (x == r.x) && (y == r.y) && (data == r.data); + } + + bool operator!=(const node_data& r) const + { + return !operator==(r); + } + + struct sorter + { + bool operator()(const node_data& left, const node_data& right) const + { + if (left.x != right.x) + return left.x < right.x; + if (left.y != right.y) + return left.y < right.y; + return left.data < right.data; + } + }; + }; + + static bool equals(::std::vector& v1, ::std::vector& v2); + + bool verify_data(::std::vector& expected) const; + + bool verify_node_iterator(const node_access& nac) const; + static bool verify_node_iterator(const node_access& nac, const node* p); + + void get_all_stored_data(::std::vector& stored_data) const; + + void dump_tree_svg(const ::std::string& fpath) const; + +private: + class array_inserter + { + public: + array_inserter(data_array_type& result) : m_result(result) + {} + + void operator()(const node* p) + { + m_result.push_back(p->data); + } + + private: + data_array_type& m_result; + }; + + class search_result_inserter + { + public: + search_result_inserter(search_results& result) : m_result(result) + {} + + void operator()(const node* p) + { + m_result.push_back(p); + } + + private: + search_results& m_result; + }; + + class data_inserter + { + public: + data_inserter(point_quad_tree& db) : m_db(db) + {} + + void operator()(const node_data& v) + { + m_db.insert(v.x, v.y, v.data); + } + + private: + point_quad_tree& m_db; + }; + + struct node_distance + { + node_quadrant_t quad; + key_type dist; + node_ptr node; + + node_distance() : quad(quad_unspecified), dist(0), node(nullptr) + {} + node_distance(node_quadrant_t _quad, key_type _dist, const node_ptr& _node) + : quad(_quad), dist(_dist), node(_node) + {} + }; + + node_ptr find_node(key_type x, key_type y) const; + const node* find_node_ptr(key_type x, key_type y) const; + node_ptr find_replacement_node(key_type x, key_type y, const node_ptr& delete_node) const; + + void find_candidate_in_quad( + key_type x, key_type y, node_distance& dx_node, node_distance& dy_node, node_distance& min_city_block_node, + const node_ptr& delete_node, node_quadrant_t quad) const; + + void adjust_quad( + const key_range_type& hatched_xrange, const key_range_type& hatched_yrange, node_ptr quad_root, direction_t dir, + reinsert_tree_array_type& insert_list); + + void set_new_root( + const key_range_type& hatched_xrange, const key_range_type& hatched_yrange, node_ptr& quad_root, + node_quadrant_t dir, reinsert_tree_array_type& insert_list); + + void insert_node(node_ptr& dest, node_ptr& node); + void reinsert_tree(node_ptr& dest, node_ptr& root); + void reinsert_tree(node_ptr& dest, node_quadrant_t quad, node_ptr& root); + void clear_all_nodes(); + void dump_node_svg(const node* p, ::std::ofstream& file) const; + void count_all_nodes(const node* p, size_t& node_count) const; + void insert_data_from(const point_quad_tree& r); + void get_all_stored_data(const node* p, ::std::vector& stored_data) const; + +private: + node_ptr m_root; + + key_range_type m_xrange; + key_range_type m_yrange; +}; + +template +point_quad_tree<_Key, _Value>::point_quad_tree() : m_root(nullptr), m_xrange(0, 0), m_yrange(0, 0) +{} + +template +point_quad_tree<_Key, _Value>::point_quad_tree(const point_quad_tree& r) + : m_root(nullptr), m_xrange(0, 0), m_yrange(0, 0) +{ + insert_data_from(r); +} + +template +point_quad_tree<_Key, _Value>::~point_quad_tree() +{ + clear_all_nodes(); +} + +template +void point_quad_tree<_Key, _Value>::insert(key_type x, key_type y, value_type data) +{ + m_xrange.first = ::std::min(m_xrange.first, x); + m_xrange.second = ::std::max(m_xrange.second, x); + m_yrange.first = ::std::min(m_yrange.first, y); + m_yrange.second = ::std::max(m_yrange.second, y); + + if (!m_root) + { + // The very first node. + m_root.reset(new node(x, y, data)); + return; + } + + node_ptr cur_node = m_root; + while (true) + { + if (cur_node->x == x && cur_node->y == y) + { + // Replace the current data with this, and we are done! + cur_node->data = data; + return; + } + + node_quadrant_t quad = cur_node->get_quadrant(x, y); + switch (quad) + { + case quad_northeast: + if (cur_node->northeast) + cur_node = cur_node->northeast; + else + { + cur_node->northeast.reset(new node(x, y, data)); + cur_node->northeast->parent = cur_node; + return; + } + break; + case quad_northwest: + if (cur_node->northwest) + cur_node = cur_node->northwest; + else + { + cur_node->northwest.reset(new node(x, y, data)); + cur_node->northwest->parent = cur_node; + return; + } + break; + case quad_southeast: + if (cur_node->southeast) + cur_node = cur_node->southeast; + else + { + cur_node->southeast.reset(new node(x, y, data)); + cur_node->southeast->parent = cur_node; + return; + } + break; + case quad_southwest: + if (cur_node->southwest) + cur_node = cur_node->southwest; + else + { + cur_node->southwest.reset(new node(x, y, data)); + cur_node->southwest->parent = cur_node; + return; + } + break; + default: + throw general_error("unknown quadrant"); + } + } + assert(!"This should never be reached."); +} + +template +void point_quad_tree<_Key, _Value>::search_region( + key_type x1, key_type y1, key_type x2, key_type y2, data_array_type& result) const +{ + using namespace std; + const node* p = m_root.get(); + array_inserter _inserter(result); + ::mdds::search_region_node(p, x1, y1, x2, y2, _inserter); +} + +template +typename point_quad_tree<_Key, _Value>::search_results point_quad_tree<_Key, _Value>::search_region( + key_type x1, key_type y1, key_type x2, key_type y2) const +{ + using namespace std; + search_results result; + const node* p = m_root.get(); + search_result_inserter _inserter(result); + ::mdds::search_region_node(p, x1, y1, x2, y2, _inserter); + return result; +} + +template +typename point_quad_tree<_Key, _Value>::value_type point_quad_tree<_Key, _Value>::find(key_type x, key_type y) const +{ + const node* p = find_node_ptr(x, y); + if (!p) + throw data_not_found(); + return p->data; +} + +template +void point_quad_tree<_Key, _Value>::remove(key_type x, key_type y) +{ + using namespace std; + node_ptr delete_node = find_node(x, y); + if (!delete_node) + // No node exists at this coordinate. + return; + +#if DEBUG_POINT_QUAD_TREE + cout << "found the node to be removed at " << delete_node->x << "," << delete_node->y << " (" << *delete_node->data + << ")" << endl; +#endif + + // Check if this is a leaf node, in which case we can just delete it + // without further processing. + if (delete_node->leaf()) + { +#if DEBUG_POINT_QUAD_TREE + cout << "deleting a leaf node." << endl; +#endif + if (delete_node.get() == m_root.get()) + m_root.reset(); + else + disconnect_node_from_parent(delete_node); + delete_node.reset(); + return; + } + + node_ptr repl_node = find_replacement_node(x, y, delete_node); + if (!repl_node) + // Non-leaf node should have at least one replacement candidate. + throw general_error("failed to find a replacement candidate node."); + + node_quadrant_t repl_quad = delete_node->get_quadrant(repl_node->x, repl_node->y); + + key_range_type xrange(delete_node->x, repl_node->x); + key_range_type yrange(delete_node->y, repl_node->y); + ensure_order(xrange); + ensure_order(yrange); + reinsert_tree_array_type insert_list; + + // Call the quadrant where the replacement node is quadrant I. Adjust the + // quadrants adjacent to quadrant I first, then adjust quadrant I + // afterwards. + switch (repl_quad) + { + case quad_northeast: + adjust_quad(xrange, yrange, delete_node->northwest, dir_south, insert_list); + adjust_quad(xrange, yrange, delete_node->southeast, dir_west, insert_list); + set_new_root(xrange, yrange, delete_node->northeast, quad_southwest, insert_list); + break; + case quad_northwest: + adjust_quad(xrange, yrange, delete_node->northeast, dir_south, insert_list); + adjust_quad(xrange, yrange, delete_node->southwest, dir_east, insert_list); + set_new_root(xrange, yrange, delete_node->northwest, quad_southeast, insert_list); + break; + case quad_southeast: + adjust_quad(xrange, yrange, delete_node->northeast, dir_west, insert_list); + adjust_quad(xrange, yrange, delete_node->southwest, dir_north, insert_list); + set_new_root(xrange, yrange, delete_node->southeast, quad_northwest, insert_list); + break; + case quad_southwest: + adjust_quad(xrange, yrange, delete_node->northwest, dir_east, insert_list); + adjust_quad(xrange, yrange, delete_node->southeast, dir_north, insert_list); + set_new_root(xrange, yrange, delete_node->southwest, quad_northeast, insert_list); + break; + default: + throw general_error("quadrant for the replacement node is unspecified."); + } + + // Reinsert all child nodes from the replacement node into the node to be + // "deleted". + switch (repl_quad) + { + case quad_northeast: + case quad_southwest: + { + node_ptr root = repl_node->northwest; + repl_node->northwest.reset(); + reinsert_tree(delete_node, quad_northwest, root); + + root = repl_node->southeast; + repl_node->southeast.reset(); + reinsert_tree(delete_node, quad_southeast, root); + } + break; + case quad_northwest: + case quad_southeast: + { + node_ptr root = repl_node->northeast; + repl_node->northeast.reset(); + reinsert_tree(delete_node, quad_northeast, root); + + root = repl_node->southwest; + repl_node->southwest.reset(); + reinsert_tree(delete_node, quad_southwest, root); + } + break; + default: + throw general_error("quadrant for the replacement node is unspecified."); + } + + // Finally, replace the node to be removed with the replacement node. + delete_node->x = repl_node->x; + delete_node->y = repl_node->y; + delete_node->data = repl_node->data; + + // Reset the parent node. + delete_node->parent = repl_node->parent; + repl_node->parent.reset(); + + switch (repl_quad) + { + case quad_northeast: + delete_node->northeast = repl_node->northeast; + repl_node->northeast.reset(); + break; + case quad_northwest: + delete_node->northwest = repl_node->northwest; + repl_node->northwest.reset(); + break; + case quad_southeast: + delete_node->southeast = repl_node->southeast; + repl_node->southeast.reset(); + break; + case quad_southwest: + delete_node->southwest = repl_node->southwest; + repl_node->southwest.reset(); + break; + default: + throw general_error("quadrant for the replacement node is unspecified."); + } + + // Lastly, re-insert all those trees that have been cut during the quad + // adjustment into the new root. + typename reinsert_tree_array_type::iterator itr = insert_list.begin(), itr_end = insert_list.end(); + for (; itr != itr_end; ++itr) + reinsert_tree(delete_node, *itr); +} + +template +void point_quad_tree<_Key, _Value>::swap(point_quad_tree& r) +{ + m_root.swap(r.m_root); + ::std::swap(m_xrange, r.m_xrange); + ::std::swap(m_yrange, r.m_yrange); +} + +template +void point_quad_tree<_Key, _Value>::clear() +{ + clear_all_nodes(); +} + +template +bool point_quad_tree<_Key, _Value>::empty() const +{ + return (m_root.get() == nullptr); +} + +template +size_t point_quad_tree<_Key, _Value>::size() const +{ + size_t node_count = 0; + count_all_nodes(m_root.get(), node_count); + return node_count; +} + +template +typename point_quad_tree<_Key, _Value>::node_access point_quad_tree<_Key, _Value>::get_node_access() const +{ + return node_access(m_root.get()); +} + +template +point_quad_tree<_Key, _Value>& point_quad_tree<_Key, _Value>::operator=(const point_quad_tree& r) +{ + m_xrange = key_range_type(0, 0); + m_yrange = key_range_type(0, 0); + clear_all_nodes(); + insert_data_from(r); + return *this; +} + +template +bool point_quad_tree<_Key, _Value>::operator==(const point_quad_tree& r) const +{ + ::std::vector v1, v2; + get_all_stored_data(v1); + r.get_all_stored_data(v2); + return equals(v1, v2); +} + +template +void point_quad_tree<_Key, _Value>::dump_tree_svg(const ::std::string& fpath) const +{ + using namespace std; + ofstream file(fpath.c_str()); + file << "" + << endl; + file << "" + << " " + << " " + << " " + << "" << endl; + + file << "" << endl; + file << "" << endl; + dump_node_svg(m_root.get(), file); + file << "" << endl; +} + +template +void draw_svg_arrow(::std::ofstream& file, const _NodePtr start, const _NodePtr end) +{ + using namespace std; + file << "" << endl; + file << "x << "\" y1=\"" << start->y << "\" x2=\"" << end->x << "\" y2=\"" << end->y + << "\" stroke-width=\"0.2\"/>" << endl; + file << "" << endl; +} + +template +void point_quad_tree<_Key, _Value>::dump_node_svg(const node* p, ::std::ofstream& file) const +{ + using namespace std; + + if (!p) + return; + + file << "x << "\" cy=\"" << p->y << "\" r=\"0.1\"" + << " fill=\"black\" stroke=\"black\"/>" << endl; + file << "x + 1 << "\" y=\"" << p->y + 2 << "\" font-size=\"1.2\" fill=\"black\">" << *p->data + << " (" << p->x << "," << p->y << ")" << endl; + + if (p->northwest) + draw_svg_arrow(file, p, p->northwest.get()); + + if (p->northeast) + draw_svg_arrow(file, p, p->northeast.get()); + + if (p->southwest) + draw_svg_arrow(file, p, p->southwest.get()); + + if (p->southeast) + draw_svg_arrow(file, p, p->southeast.get()); + + dump_node_svg(p->northeast.get(), file); + dump_node_svg(p->northwest.get(), file); + dump_node_svg(p->southeast.get(), file); + dump_node_svg(p->southwest.get(), file); +} + +template +bool point_quad_tree<_Key, _Value>::equals(::std::vector& v1, ::std::vector& v2) +{ + using namespace std; + + if (v1.size() != v2.size()) + return false; + + sort(v1.begin(), v1.end(), typename node_data::sorter()); + sort(v2.begin(), v2.end(), typename node_data::sorter()); + + typename vector::const_iterator itr1 = v1.begin(), itr1_end = v1.end(), itr2 = v2.begin(), + itr2_end = v2.end(); + + for (; itr1 != itr1_end; ++itr1, ++itr2) + { + if (itr2 == itr2_end) + return false; + + if (*itr1 != *itr2) + return false; + } + if (itr2 != itr2_end) + return false; + + return true; +} + +template +void point_quad_tree<_Key, _Value>::get_all_stored_data(::std::vector& stored_data) const +{ + stored_data.clear(); + if (!m_root) + return; + + get_all_stored_data(m_root.get(), stored_data); +} + +template +void point_quad_tree<_Key, _Value>::count_all_nodes(const node* p, size_t& node_count) const +{ + if (!p) + return; + + ++node_count; + + count_all_nodes(p->northeast.get(), node_count); + count_all_nodes(p->northwest.get(), node_count); + count_all_nodes(p->southeast.get(), node_count); + count_all_nodes(p->southwest.get(), node_count); +} + +template +void point_quad_tree<_Key, _Value>::insert_data_from(const point_quad_tree& r) +{ + using namespace std; + vector all_data; + r.get_all_stored_data(all_data); + for_each(all_data.begin(), all_data.end(), data_inserter(*this)); +} + +template +bool point_quad_tree<_Key, _Value>::verify_data(::std::vector& expected) const +{ + ::std::vector stored; + get_all_stored_data(stored); + return equals(stored, expected); +} + +template +bool point_quad_tree<_Key, _Value>::verify_node_iterator(const node_access& nac) const +{ + return verify_node_iterator(nac, m_root.get()); +} + +template +bool point_quad_tree<_Key, _Value>::verify_node_iterator(const node_access& nac, const node* p) +{ + if (!nac) + return (p == nullptr); + + if (!p) + return false; + + if (!verify_node_iterator(nac.northeast(), p->northeast.get())) + return false; + if (!verify_node_iterator(nac.northwest(), p->northwest.get())) + return false; + if (!verify_node_iterator(nac.southeast(), p->southeast.get())) + return false; + if (!verify_node_iterator(nac.southwest(), p->southwest.get())) + return false; + + return true; +} + +template +void point_quad_tree<_Key, _Value>::get_all_stored_data(const node* p, ::std::vector& stored_data) const +{ + if (!p) + return; + + stored_data.push_back(node_data(p->x, p->y, p->data)); + + get_all_stored_data(p->northeast.get(), stored_data); + get_all_stored_data(p->northwest.get(), stored_data); + get_all_stored_data(p->southeast.get(), stored_data); + get_all_stored_data(p->southwest.get(), stored_data); +} + +template +typename point_quad_tree<_Key, _Value>::node_ptr point_quad_tree<_Key, _Value>::find_node(key_type x, key_type y) const +{ + node_ptr cur_node = m_root; + while (cur_node) + { + if (cur_node->x == x && cur_node->y == y) + { + // Found the node. + return cur_node; + } + + node_quadrant_t quad = cur_node->get_quadrant(x, y); + switch (quad) + { + case quad_northeast: + if (!cur_node->northeast) + return node_ptr(); + cur_node = cur_node->northeast; + break; + case quad_northwest: + if (!cur_node->northwest) + return node_ptr(); + cur_node = cur_node->northwest; + break; + case quad_southeast: + if (!cur_node->southeast) + return node_ptr(); + cur_node = cur_node->southeast; + break; + case quad_southwest: + if (!cur_node->southwest) + return node_ptr(); + cur_node = cur_node->southwest; + break; + default: + throw general_error("unknown quadrant"); + } + } + return node_ptr(); +} + +template +const typename point_quad_tree<_Key, _Value>::node* point_quad_tree<_Key, _Value>::find_node_ptr( + key_type x, key_type y) const +{ + const node* cur_node = m_root.get(); + while (cur_node) + { + if (cur_node->x == x && cur_node->y == y) + { + // Found the node. + return cur_node; + } + + node_quadrant_t quad = cur_node->get_quadrant(x, y); + switch (quad) + { + case quad_northeast: + if (!cur_node->northeast) + return nullptr; + cur_node = cur_node->northeast.get(); + break; + case quad_northwest: + if (!cur_node->northwest) + return nullptr; + cur_node = cur_node->northwest.get(); + break; + case quad_southeast: + if (!cur_node->southeast) + return nullptr; + cur_node = cur_node->southeast.get(); + break; + case quad_southwest: + if (!cur_node->southwest) + return nullptr; + cur_node = cur_node->southwest.get(); + break; + default: + throw general_error("unknown quadrant"); + } + } + return nullptr; +} + +template +typename point_quad_tree<_Key, _Value>::node_ptr point_quad_tree<_Key, _Value>::find_replacement_node( + key_type x, key_type y, const node_ptr& delete_node) const +{ + using namespace std; + + // Try to get a replacement candidate in each quadrant. + node_distance dx_node, dy_node, min_city_block_node; + +#if DEBUG_POINT_QUAD_TREE + cout << "northeast" << endl; +#endif + find_candidate_in_quad(x, y, dx_node, dy_node, min_city_block_node, delete_node, quad_northeast); + +#if DEBUG_POINT_QUAD_TREE + cout << "northwest" << endl; +#endif + find_candidate_in_quad(x, y, dx_node, dy_node, min_city_block_node, delete_node, quad_northwest); + +#if DEBUG_POINT_QUAD_TREE + cout << "southwest" << endl; +#endif + find_candidate_in_quad(x, y, dx_node, dy_node, min_city_block_node, delete_node, quad_southwest); + +#if DEBUG_POINT_QUAD_TREE + cout << "southeast" << endl; +#endif + find_candidate_in_quad(x, y, dx_node, dy_node, min_city_block_node, delete_node, quad_southeast); + + // Check Criterion 1. + +#if DEBUG_POINT_QUAD_TREE + if (dx_node.node) + cout << "node closest to x axis: " << *dx_node.node->data << " (dx=" << dx_node.dist << ")" << endl; + + if (dy_node.node) + cout << "node closest to y axis: " << *dy_node.node->data << " (dy=" << dy_node.dist << ")" << endl; +#endif + + if (dx_node.node == dy_node.node && ((dx_node.quad == quad_northwest) || (dx_node.quad == quad_southeast))) + { +#if DEBUG_POINT_QUAD_TREE + cout << "node that satisfies Criterion 1: " << *dx_node.node->data << endl; +#endif + return dx_node.node; + } + else + { +#if DEBUG_POINT_QUAD_TREE + cout << "unable to find node that satisfies Criterion 1." << endl; +#endif + } + + // Move on to Criterion 2. + + if (min_city_block_node.node) + { +#if DEBUG_POINT_QUAD_TREE + cout << "node that satisfies Criterion 2: " << *min_city_block_node.node->data + << " (dist=" << min_city_block_node.dist << ")" << endl; +#endif + return min_city_block_node.node; + } + + return node_ptr(); +} + +template +void point_quad_tree<_Key, _Value>::find_candidate_in_quad( + key_type x, key_type y, node_distance& dx_node, node_distance& dy_node, node_distance& min_city_block_node, + const node_ptr& delete_node, node_quadrant_t quad) const +{ + using namespace std; + + node_ptr repl_node = delete_node->get_quadrant_node(quad); + if (!repl_node) + { + // No candidate in this quadrant. +#if DEBUG_POINT_QUAD_TREE + cout << " no candidate in this quadrant" << endl; +#endif + return; + } + + node_quadrant_t oppo_quad = opposite(quad); + while (repl_node->has_quadrant_node(oppo_quad)) + repl_node = repl_node->get_quadrant_node(oppo_quad); + +#if DEBUG_POINT_QUAD_TREE + cout << " candidate: " << repl_node->x << "," << repl_node->y << " (" << *repl_node->data << ")" << endl; +#endif + + // Calculate its distance to each of the borders. + key_type dx = repl_node->x > x ? repl_node->x - x : x - repl_node->x; + key_type dy = repl_node->y > y ? repl_node->y - y : y - repl_node->y; +#if DEBUG_POINT_QUAD_TREE + cout << " dx = " << dx << ", dy = " << dy << endl; +#endif + + if (!dx_node.node || dx_node.dist > dx) + dx_node = node_distance(quad, dx, repl_node); + if (!dy_node.node || dy_node.dist > dy) + dy_node = node_distance(quad, dy, repl_node); + + if (!min_city_block_node.node || min_city_block_node.dist > (dx + dy)) + min_city_block_node = node_distance(quad_unspecified, dx + dy, repl_node); +} + +template +void point_quad_tree<_Key, _Value>::adjust_quad( + const key_range_type& hatched_xrange, const key_range_type& hatched_yrange, node_ptr quad_root, direction_t dir, + reinsert_tree_array_type& insert_list) +{ + using namespace std; + + if (!quad_root) + return; + +#if DEBUG_POINT_QUAD_TREE + cout << "adjust_quad: checking " << *quad_root->data << " (" << quad_root->x << "," << quad_root->y << ")" << endl; +#endif + + if ((hatched_xrange.first <= quad_root->x && quad_root->x <= hatched_xrange.second) || + (hatched_yrange.first <= quad_root->y && quad_root->y <= hatched_yrange.second)) + { +#if DEBUG_POINT_QUAD_TREE + cout << " " << *quad_root->data << " is in the hatched region" << endl; +#endif + // Insert the whole tree, including the root, into the insert list. + disconnect_node_from_parent(quad_root); + quad_root->parent.reset(); + insert_list.push_back(quad_root); + return; + } + + switch (dir) + { + case dir_east: + adjust_quad(hatched_xrange, hatched_yrange, quad_root->northeast, dir_east, insert_list); + adjust_quad(hatched_xrange, hatched_yrange, quad_root->southeast, dir_east, insert_list); + break; + case dir_north: + adjust_quad(hatched_xrange, hatched_yrange, quad_root->northeast, dir_north, insert_list); + adjust_quad(hatched_xrange, hatched_yrange, quad_root->northwest, dir_north, insert_list); + break; + case dir_south: + adjust_quad(hatched_xrange, hatched_yrange, quad_root->southeast, dir_south, insert_list); + adjust_quad(hatched_xrange, hatched_yrange, quad_root->southwest, dir_south, insert_list); + break; + case dir_west: + adjust_quad(hatched_xrange, hatched_yrange, quad_root->northwest, dir_west, insert_list); + adjust_quad(hatched_xrange, hatched_yrange, quad_root->southwest, dir_west, insert_list); + break; + default:; + } +} + +template +void point_quad_tree<_Key, _Value>::set_new_root( + const key_range_type& hatched_xrange, const key_range_type& hatched_yrange, node_ptr& quad_root, + node_quadrant_t dir, reinsert_tree_array_type& insert_list) +{ + node_ptr cur_node = quad_root; + while (cur_node) + { + switch (dir) + { + case quad_northeast: + adjust_quad(hatched_xrange, hatched_yrange, cur_node->southeast, dir_east, insert_list); + adjust_quad(hatched_xrange, hatched_yrange, cur_node->northwest, dir_north, insert_list); + break; + case quad_northwest: + adjust_quad(hatched_xrange, hatched_yrange, cur_node->northeast, dir_north, insert_list); + adjust_quad(hatched_xrange, hatched_yrange, cur_node->southwest, dir_west, insert_list); + break; + case quad_southeast: + adjust_quad(hatched_xrange, hatched_yrange, cur_node->northeast, dir_east, insert_list); + adjust_quad(hatched_xrange, hatched_yrange, cur_node->southwest, dir_south, insert_list); + break; + case quad_southwest: + adjust_quad(hatched_xrange, hatched_yrange, cur_node->northwest, dir_west, insert_list); + adjust_quad(hatched_xrange, hatched_yrange, cur_node->southeast, dir_south, insert_list); + break; + default: + throw general_error("unspecified quadrant"); + } + cur_node = cur_node->get_quadrant_node(dir); + } +} + +template +void point_quad_tree<_Key, _Value>::insert_node(node_ptr& dest, node_ptr& this_node) +{ + node_ptr cur_node = dest; + while (true) + { + if (cur_node->x == this_node->x && cur_node->y == this_node->y) + { + // When inserting a node instance directly (likely as part of tree + // re-insertion), we are not supposed to have another node at + // identical position. + throw general_error("node with identical position encountered."); + } + + node_quadrant_t quad = cur_node->get_quadrant(this_node->x, this_node->y); + switch (quad) + { + case quad_northeast: + if (cur_node->northeast) + cur_node = cur_node->northeast; + else + { + cur_node->northeast = this_node; + this_node->parent = cur_node; + return; + } + break; + case quad_northwest: + if (cur_node->northwest) + cur_node = cur_node->northwest; + else + { + cur_node->northwest = this_node; + this_node->parent = cur_node; + return; + } + break; + case quad_southeast: + if (cur_node->southeast) + cur_node = cur_node->southeast; + else + { + cur_node->southeast = this_node; + this_node->parent = cur_node; + return; + } + break; + case quad_southwest: + if (cur_node->southwest) + cur_node = cur_node->southwest; + else + { + cur_node->southwest = this_node; + this_node->parent = cur_node; + return; + } + break; + default: + throw general_error("unknown quadrant"); + } + } + assert(!"This should never be reached."); +} + +template +void point_quad_tree<_Key, _Value>::reinsert_tree(node_ptr& dest, node_ptr& root) +{ + assert(dest); // Destination node should not be null. + + if (!root) + // Nothing to re-insert. Bail out. + return; + + if (root->northeast) + { + reinsert_tree(dest, root->northeast); + root->northeast.reset(); + } + if (root->northwest) + { + reinsert_tree(dest, root->northwest); + root->northwest.reset(); + } + if (root->southeast) + { + reinsert_tree(dest, root->southeast); + root->southeast.reset(); + } + if (root->southwest) + { + reinsert_tree(dest, root->southwest); + root->southwest.reset(); + } + + root->parent.reset(); + insert_node(dest, root); +} + +template +void point_quad_tree<_Key, _Value>::reinsert_tree(node_ptr& dest, node_quadrant_t quad, node_ptr& root) +{ + if (!root) + // Nothing to re-insert. Bail out. + return; + + switch (quad) + { + case quad_northeast: + if (dest->northeast) + reinsert_tree(dest->northeast, root); + else + { + dest->northeast = root; + root->parent = dest; + } + break; + case quad_northwest: + if (dest->northwest) + reinsert_tree(dest->northwest, root); + else + { + dest->northwest = root; + root->parent = dest; + } + break; + case quad_southeast: + if (dest->southeast) + reinsert_tree(dest->southeast, root); + else + { + dest->southeast = root; + root->parent = dest; + } + break; + case quad_southwest: + if (dest->southwest) + reinsert_tree(dest->southwest, root); + else + { + dest->southwest = root; + root->parent = dest; + } + break; + default: + throw general_error("reinsert_tree: quadrant unspecified"); + } +} + +template +void point_quad_tree<_Key, _Value>::clear_all_nodes() +{ + ::mdds::disconnect_all_nodes(m_root); + m_root.reset(); +} + +} // namespace mdds + +#endif diff --git a/include/mdds/quad_node.hpp b/include/mdds/quad_node.hpp new file mode 100644 index 0000000..d69efb3 --- /dev/null +++ b/include/mdds/quad_node.hpp @@ -0,0 +1,375 @@ +/************************************************************************* + * + * Copyright (c) 2010 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef __MDDS_QUAD_NODE_HPP__ +#define __MDDS_QUAD_NODE_HPP__ + +#include "mdds/global.hpp" + +#include + +#include + +namespace mdds { + +#ifdef MDDS_DEBUG_NODE_BASE +size_t node_instance_count = 0; +inline size_t get_node_instance_count() +{ + return node_instance_count; +} +#endif + +/** + * NW | NE + * -----|----- + * SW | SE + */ +enum node_quadrant_t +{ + quad_northeast, + quad_northwest, + quad_southeast, + quad_southwest, + quad_unspecified +}; + +/** + * NW | N | NE + * -----|-----|----- + * W | C | E + * -----|-----|----- + * SW | S | SE + */ +enum search_region_space_t +{ + region_northwest, + region_north, + region_northeast, + region_east, + region_southeast, + region_south, + region_southwest, + region_west, + region_center +}; + +/** + * N + * | + * | + * W -----+----- E + * | + * | + * S + */ +enum direction_t +{ + dir_north, + dir_west, + dir_south, + dir_east +}; + +inline node_quadrant_t opposite(node_quadrant_t quad) +{ + switch (quad) + { + case quad_northeast: + return quad_southwest; + case quad_northwest: + return quad_southeast; + case quad_southeast: + return quad_northwest; + case quad_southwest: + return quad_northeast; + case quad_unspecified: + default:; + } + return quad_unspecified; +} + +template +struct quad_node_base +{ + typedef _Key key_type; + typedef _NodePtr node_ptr; + typedef _NodeType node_type; + + size_t refcount; + + node_ptr parent; + node_ptr northeast; + node_ptr northwest; + node_ptr southeast; + node_ptr southwest; + + key_type x; + key_type y; + + quad_node_base(key_type _x, key_type _y) + : refcount(0), parent(nullptr), northeast(nullptr), northwest(nullptr), southeast(nullptr), southwest(nullptr), + x(_x), y(_y) + { +#ifdef MDDS_DEBUG_NODE_BASE + ++node_instance_count; +#endif + } + + /** + * When copying node, only the stored values should be copied. + * Connections to the parent and the neighboring nodes must not be copied. + */ + quad_node_base(const quad_node_base& r) + : refcount(0), parent(nullptr), northeast(nullptr), northwest(nullptr), southeast(nullptr), southwest(nullptr), + x(r.x), y(r.y) + { +#ifdef MDDS_DEBUG_NODE_BASE + ++node_instance_count; +#endif + } + + bool leaf() const + { + return !northeast && !northwest && !southeast && !southwest; + } + + bool operator==(const quad_node_base& r) const + { + return x == r.x && y == r.y; + } + + /** + * Like the copy constructor, only the stored values should be copied. + */ + quad_node_base& operator=(const quad_node_base& r) + { + if (this == &r) + // assignment to self. + return *this; + + x = r.x; + y = r.y; + return *this; + } + + ~quad_node_base() + { +#ifdef MDDS_DEBUG_NODE_BASE + --node_instance_count; +#endif + static_cast(this)->dispose(); + } + + /** + * Return the quadrant of specified point in reference to this node. + * + * @return quadrant where the other node is located in reference to this + * node. + */ + node_quadrant_t get_quadrant(key_type other_x, key_type other_y) const + { + if (other_x < x) + // west + return other_y < y ? quad_northwest : quad_southwest; + + // east + return other_y < y ? quad_northeast : quad_southeast; + } + + bool has_quadrant_node(node_quadrant_t quad) const + { + switch (quad) + { + case quad_northeast: + return northeast.get() != nullptr; + case quad_northwest: + return northwest.get() != nullptr; + case quad_southeast: + return southeast.get() != nullptr; + case quad_southwest: + return southwest.get() != nullptr; + default: + throw general_error("unknown quadrant type"); + } + return false; + } + + node_ptr get_quadrant_node(node_quadrant_t quad) const + { + node_ptr ret; + switch (quad) + { + case quad_northeast: + ret = northeast; + break; + case quad_northwest: + ret = northwest; + break; + case quad_southeast: + ret = southeast; + break; + case quad_southwest: + ret = southwest; + break; + default: + throw general_error("unknown quadrant type"); + } + return ret; + } +}; + +template +inline void intrusive_ptr_add_ref(::mdds::quad_node_base<_NodePtr, _NodeType, _Key>* p) +{ + ++p->refcount; +} + +template +inline void intrusive_ptr_release(::mdds::quad_node_base<_NodePtr, _NodeType, _Key>* p) +{ + --p->refcount; + if (!p->refcount) + delete p; +} + +template +void disconnect_node_from_parent(_NodePtr p) +{ + if (!p || !p->parent) + // Nothing to do. + return; + + _NodePtr& parent = p->parent; + if (parent->northeast && parent->northeast == p) + { + parent->northeast.reset(); + } + else if (parent->northwest && parent->northwest == p) + { + parent->northwest.reset(); + } + else if (parent->southwest && parent->southwest == p) + { + parent->southwest.reset(); + } + else if (parent->southeast && parent->southeast == p) + { + parent->southeast.reset(); + } + else + throw general_error("parent node doesn't lead to the deleted node."); +} + +template +void disconnect_all_nodes(_NodePtr p) +{ + if (!p) + return; + + if (p->northeast) + { + disconnect_all_nodes(p->northeast); + p->northeast.reset(); + } + + if (p->northwest) + { + disconnect_all_nodes(p->northwest); + p->northwest.reset(); + } + + if (p->southeast) + { + disconnect_all_nodes(p->southeast); + p->southeast.reset(); + } + + if (p->southwest) + { + disconnect_all_nodes(p->southwest); + p->southwest.reset(); + } + + p->parent.reset(); +} + +template +search_region_space_t get_search_region_space(_NodeType* p, _Key x1, _Key y1, _Key x2, _Key y2) +{ + typedef _Key key_type; + + key_type x = p->x, y = p->y; + if (x < x1) + { + // western region + if (y < y1) + { + return region_northwest; + } + else if (y1 <= y && y <= y2) + { + return region_west; + } + + assert(y2 < y); + return region_southwest; + } + else if (x1 <= x && x <= x2) + { + // central region + if (y < y1) + { + return region_north; + } + else if (y1 <= y && y <= y2) + { + return region_center; + } + + assert(y2 < y); + return region_south; + } + + // eastern region + assert(x2 < x); + if (y < y1) + { + return region_northeast; + } + else if (y1 <= y && y <= y2) + { + return region_east; + } + + assert(y2 < y); + return region_southeast; +} + +} // namespace mdds + +#endif diff --git a/include/mdds/ref_pair.hpp b/include/mdds/ref_pair.hpp new file mode 100644 index 0000000..ea91ef7 --- /dev/null +++ b/include/mdds/ref_pair.hpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2020 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_REF_PAIR_HPP +#define INCLUDED_MDDS_REF_PAIR_HPP + +#include + +namespace mdds { namespace detail { + +template +struct ref_pair +{ + using first_type = typename std::add_lvalue_reference::type; + using second_type = typename std::add_lvalue_reference::type; + + first_type first; + second_type second; + + ref_pair(first_type _first, second_type _second) : first(_first), second(_second) + {} + + ref_pair(const ref_pair& other) = default; + + bool operator==(const std::pair::type, typename std::decay::type>& other) const + { + return first == other.first && second == other.second; + } + + bool operator!=(const std::pair::type, typename std::decay::type>& other) const + { + return !operator==(other); + } + + bool operator==(const ref_pair& other) const + { + return first == other.first && second == other.second; + } + + bool operator!=(const ref_pair& other) const + { + return !operator==(other); + } + + ref_pair* operator->() + { + return this; + } +}; + +}} // namespace mdds::detail + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/rtree.hpp b/include/mdds/rtree.hpp new file mode 100644 index 0000000..b21ed8f --- /dev/null +++ b/include/mdds/rtree.hpp @@ -0,0 +1,830 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2018 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_RTREE_HPP +#define INCLUDED_MDDS_RTREE_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace mdds { + +namespace detail { namespace rtree { + +struct default_rtree_traits +{ + /** + * Number of dimensions in bounding rectangles. + */ + static constexpr size_t dimensions = 2; + + /** + * Minimum number of child nodes that must be present in each directory + * node. Exception is the root node, which is allowed to have less than + * the minimum number of nodes, but only when it's a leaf directory node. + */ + static constexpr size_t min_node_size = 40; + + /** + * Maximum number of child nodes that each directory node is allowed to + * have. There are no exceptions to this rule. + */ + static constexpr size_t max_node_size = 100; + + /** + * Maximum depth a tree is allowed to have. + */ + static constexpr size_t max_tree_depth = 100; + + /** + * A flag to determine whether or not to perform forced reinsertion when a + * directory node overflows, before attempting to split the node. + */ + static constexpr bool enable_forced_reinsertion = true; + + /** + * Number of nodes to get re-inserted during forced reinsertion. This + * should be roughly 30% of max_node_size + 1, and should not be greater + * than max_node_size - min_node_size + 1. + */ + static constexpr size_t reinsertion_size = 30; +}; + +struct integrity_check_properties +{ + /** + * When true, the integrity check will throw an exception on the first + * validation failure. When false, it will run through the entire tree + * and report all encountered validation failures then throw an exception + * if there is at least one failure. + */ + bool throw_on_first_error = true; + + /** + * When true, a node containing children less than the minimum node size + * will be treated as an error. + */ + bool error_on_min_node_size = true; +}; + +enum class node_type +{ + unspecified, + directory_leaf, + directory_nonleaf, + value +}; + +enum class export_tree_type +{ + /** + * Textural representation of a tree structure. Indent levels represent + * depths, and each line represents a single node. + */ + formatted_node_properties, + + /** + * The extents of all directory and value nodes are exported as Wavefront + * .obj format. Only 2 dimensional trees are supported for now. + * + * For a 2-dimensional tree, each depth is represented by a 2D plane + * filled with rectangles representing the extents of either value or + * directory nodes at that depth level. The depth planes are then stacked + * vertically. + */ + extent_as_obj, + + /** + * The extents of all directory and value nodes are exported as a scalable + * vector graphics (SVG) format. Only 2 dimensional trees are supported. + */ + extent_as_svg +}; + +enum class search_type +{ + /** + * Pick up all objects whose extents overlap with the specified search + * extent. + */ + overlap, + + /** + * Pick up all objects whose extents exactly match the specified search + * extent. + */ + match, +}; + +template +struct ptr_to_string; + +}} // namespace detail::rtree + +template +class rtree +{ + using traits_type = Traits; + + constexpr static size_t max_dist_size = traits_type::max_node_size - traits_type::min_node_size * 2 + 2; + +public: + using key_type = KeyT; + using value_type = ValueT; + + struct point_type + { + key_type d[traits_type::dimensions]; + + point_type(); + point_type(std::initializer_list vs); + + std::string to_string() const; + + bool operator==(const point_type& other) const; + bool operator!=(const point_type& other) const; + }; + + struct extent_type + { + point_type start; + point_type end; + + extent_type(); + extent_type(const point_type& _start, const point_type& _end); + + std::string to_string() const; + + bool is_point() const; + + bool operator==(const extent_type& other) const; + bool operator!=(const extent_type& other) const; + + /** + * Determine whether or not the specified point lies within this + * extent. + * + * @param pt point to query with. + * + * @return true if the point lies within this extent, or false + * otherwise. + */ + bool contains(const point_type& pt) const; + + /** + * Determine whether or not the specified extent lies entirely + * within this extent. + * + * @param bb extent to query with. + * + * @return true if the specified extent lies entirely within this + * extent, or otherwise false. + */ + bool contains(const extent_type& bb) const; + + /** + * Determine whether or not the specified extent overlaps with this + * extent either partially or fully. + * + * @param bb extent to query with. + * + * @return true if the specified extent overlaps with this extent, or + * otherwise false. + */ + bool intersects(const extent_type& bb) const; + + /** + * Determine whether or not another bounding box is within this + * bounding box and shares a part of its boundaries. + */ + bool contains_at_boundary(const extent_type& other) const; + }; + + using node_type = detail::rtree::node_type; + using export_tree_type = detail::rtree::export_tree_type; + using search_type = detail::rtree::search_type; + using integrity_check_properties = detail::rtree::integrity_check_properties; + + struct node_properties + { + node_type type; + extent_type extent; + }; + +private: + struct node; + struct directory_node; + + /** + * This class is intentionally only movable and non-copyable, to prevent + * accidental copying of its object. To "copy" this class, you must use + * its clone() method explicitly. + */ + struct node_store + { + node_type type; + extent_type extent; + node_store* parent; + node* node_ptr; + size_t count; + + bool valid_pointer; + + node_store(const node_store&) = delete; + node_store& operator=(const node_store&) = delete; + + node_store(); + node_store(node_store&& r); + node_store(node_type _type, const extent_type& _extent, node* _node_ptr); + ~node_store(); + + node_store clone() const; + + static node_store create_leaf_directory_node(); + static node_store create_nonleaf_directory_node(); + static node_store create_value_node(const extent_type& extent, value_type&& v); + static node_store create_value_node(const extent_type& extent, const value_type& v); + + node_store& operator=(node_store&& other); + + /** + * Re-calculate the extent based on its current children. + * + * @return true if the extent has changed, false otherwise. + */ + bool pack(); + + /** + * Re-calculate the extent of the parent nodes recursively all the way + * up to the root node. + */ + void pack_upward(); + + bool is_directory() const; + bool is_root() const; + bool exceeds_capacity() const; + + void swap(node_store& other); + + /** + * Have all its child nodes update their parent pointers if the memory + * location of this node has been invalidated. Run the tree + * recursively until no more invalid pointers have been found. + */ + void reset_parent_pointers_of_children(); + + /** + * Update its parent pointer and all its child nodes' parent pointers + * if the memory location of this node has been invalidated. Run the + * tree recursively until no more invalid pointers have been found. + */ + void reset_parent_pointers(); + + directory_node* get_directory_node(); + const directory_node* get_directory_node() const; + + bool erase_child(const node_store* p); + }; + + using dir_store_type = std::deque; + + struct dir_store_segment + { + typename dir_store_type::iterator begin; + typename dir_store_type::iterator end; + size_t size; + + dir_store_segment() : size(0) + {} + + dir_store_segment( + typename dir_store_type::iterator _begin, typename dir_store_type::iterator _end, size_t _size) + : begin(std::move(_begin)), end(std::move(_end)), size(_size) + {} + }; + + struct distribution + { + dir_store_segment g1; + dir_store_segment g2; + + distribution(size_t dist, dir_store_type& nodes) + { + g1.begin = nodes.begin(); + g1.end = g1.begin; + g1.size = traits_type::min_node_size - 1 + dist; + std::advance(g1.end, g1.size); + + g2.begin = g1.end; + g2.end = nodes.end(); + g2.size = nodes.size() - g1.size; + } + }; + + struct node + { + node(); + node(const node&) = delete; + ~node(); + }; + + struct value_node : public node + { + value_type value; + + value_node() = delete; + value_node(value_type&& _value); + value_node(const value_type& _value); + ~value_node(); + }; + + /** + * Directory node can either contain all value nodes, or all directory + * nodes as its child nodes. + */ + struct directory_node : public node + { + dir_store_type children; + + directory_node(const directory_node&) = delete; + directory_node& operator=(const directory_node&) = delete; + + directory_node(); + ~directory_node(); + + bool erase(const node_store* p); + + node_store* get_child_with_minimal_overlap(const extent_type& bb); + node_store* get_child_with_minimal_area_enlargement(const extent_type& bb); + + extent_type calc_extent() const; + key_type calc_overlap_cost(const extent_type& bb) const; + bool has_leaf_directory() const; + }; + + struct orphan_node_entry + { + node_store ns; + size_t depth; + }; + + using orphan_node_entries_type = std::deque; + + struct insertion_point + { + node_store* ns; + size_t depth; + }; + +public: + template + class search_results_base + { + friend class rtree; + + protected: + using node_store_type = NS; + + struct entry + { + node_store_type* ns; + size_t depth; + + entry(node_store_type* _ns, size_t _depth); + }; + + using store_type = std::vector; + store_type m_store; + + void add_node_store(node_store_type* ns, size_t depth); + }; + + class const_iterator; + class iterator; + class search_results; + + class const_search_results : public search_results_base + { + using base_type = search_results_base; + using base_type::m_store; + + public: + const_iterator cbegin() const; + const_iterator cend() const; + const_iterator begin() const; + const_iterator end() const; + }; + + class search_results : public search_results_base + { + using base_type = search_results_base; + using base_type::m_store; + + public: + iterator begin(); + iterator end(); + }; + + template + class iterator_base + { + public: + using store_iterator_type = _StoreIter; + using self_iterator_type = _SelfIter; + + protected: + store_iterator_type m_pos; + + iterator_base(store_iterator_type pos); + + public: + // iterator traits + using value_type = _ValueT; + using pointer = value_type*; + using reference = value_type&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::bidirectional_iterator_tag; + + bool operator==(const self_iterator_type& other) const; + bool operator!=(const self_iterator_type& other) const; + + self_iterator_type& operator++(); + self_iterator_type operator++(int); + + self_iterator_type& operator--(); + self_iterator_type operator--(int); + + const extent_type& extent() const; + size_t depth() const; + }; + + class const_iterator + : public iterator_base< + const_iterator, typename const_search_results::store_type::const_iterator, const rtree::value_type> + { + using base_type = iterator_base< + const_iterator, typename const_search_results::store_type::const_iterator, const rtree::value_type>; + + using store_type = typename const_search_results::store_type; + using base_type::m_pos; + using typename base_type::store_iterator_type; + + friend class rtree; + + const_iterator(store_iterator_type pos); + + public: + using typename base_type::value_type; + + value_type& operator*() const + { + return static_cast(m_pos->ns->node_ptr)->value; + } + + value_type* operator->() const + { + return &operator*(); + } + }; + + class iterator : public iterator_base + { + using base_type = iterator_base; + + using store_type = typename const_search_results::store_type; + using base_type::m_pos; + using typename base_type::store_iterator_type; + + friend class rtree; + + iterator(store_iterator_type pos); + + public: + using typename base_type::value_type; + + value_type& operator*() + { + return static_cast(m_pos->ns->node_ptr)->value; + } + + value_type* operator->() + { + return &operator*(); + } + }; + + /** + * Loader optimized for loading a large number of value objects. A + * resultant tree will have a higher chance of being well balanced than if + * the value objects were inserted individually into the tree. + */ + class bulk_loader + { + dir_store_type m_store; + + public: + bulk_loader(); + + void insert(const extent_type& extent, value_type&& value); + void insert(const extent_type& extent, const value_type& value); + + void insert(const point_type& position, value_type&& value); + void insert(const point_type& position, const value_type& value); + + rtree pack(); + + private: + void pack_level(dir_store_type& store, size_t depth); + + void insert_impl(const extent_type& extent, value_type&& value); + void insert_impl(const extent_type& extent, const value_type& value); + }; + + rtree(); + rtree(rtree&& other); + rtree(const rtree& other); + +private: + rtree(node_store&& root); // only used internally by bulk_loader. + +public: + ~rtree(); + + rtree& operator=(const rtree& other); + + rtree& operator=(rtree&& other); + + /** + * Insert a new value associated with a bounding box. The new value + * object will be moved into the container. + * + * @param extent bounding box associated with the value. + * @param value value being inserted. + */ + void insert(const extent_type& extent, value_type&& value); + + /** + * Insert a new value associated with a bounding box. A copy of the new + * value object will be placed into the container. + * + * @param extent bounding box associated with the value. + * @param value value being inserted. + */ + void insert(const extent_type& extent, const value_type& value); + + /** + * Insert a new value associated with a point. The new value object will + * be moved into the container. + * + * @param position point associated with the value. + * @param value value being inserted. + */ + void insert(const point_type& position, value_type&& value); + + /** + * Insert a new value associated with a point. A copy of the new value + * object will be placed into the container. + * + * @param position point associated with the value. + * @param value value being inserted. + */ + void insert(const point_type& position, const value_type& value); + + /** + * Search the tree and collect all value objects whose extents either + * contain the specified point, or exactly match the specified point. + * + * @param pt reference point to use for the search. + * @param st search type that determines the satisfying condition of the + * search with respect to the reference point. + * + * @return collection of all value objects that satisfy the specified + * search condition. This collection is immutable. + */ + const_search_results search(const point_type& pt, search_type st) const; + + /** + * Search the tree and collect all value objects whose extents either + * contain the specified point, or exactly match the specified point. + * + * @param pt reference point to use for the search. + * @param st search type that determines the satisfying condition of the + * search with respect to the reference point. + * + * @return collection of all value objects that satisfy the specified + * search condition. This collection is mutable. + */ + search_results search(const point_type& pt, search_type st); + + /** + * Search the tree and collect all value objects whose extents either + * overlaps with the specified extent, or exactly match the specified + * extent. + * + * @param extent reference extent to use for the search. + * @param st search type that determines the satisfying condition of the + * search with respect to the reference extent. + * + * @return collection of all value objects that satisfy the specified + * search condition. This collection is immutable. + */ + const_search_results search(const extent_type& extent, search_type st) const; + + /** + * Search the tree and collect all value objects whose extents either + * overlaps with the specified extent, or exactly match the specified + * extent. + * + * @param extent reference extent to use for the search. + * @param st search type that determines the satisfying condition of the + * search with respect to the reference extent. + * + * @return collection of all value objects that satisfy the specified + * search condition. This collection is mutable. + */ + search_results search(const extent_type& extent, search_type st); + + /** + * Erase the value object referenced by the iterator passed to this + * method. + * + *

The iterator object will become invalid if the call results in an + * erasure of a value.

+ * + * @param pos iterator that refernces the value object to erase. + */ + void erase(const const_iterator& pos); + + /** + * Erase the value object referenced by the iterator passed to this + * method. + * + *

The iterator object will become invalid if the call results in an + * erasure of a value.

+ * + * @param pos iterator that refernces the value object to erase. + */ + void erase(const iterator& pos); + + /** + * Get the minimum bounding extent of the root node of the tree. The + * extent returned from this method is the minimum extent that contains + * the extents of all objects stored in the tree. + * + * @return immutable reference to the extent of the root node of the tree. + */ + const extent_type& extent() const; + + /** + * Check whether or not the tree stores any objects. + * + * @return true if the tree is empty, otherwise false. + */ + bool empty() const; + + /** + * Return the number of value nodes currently stored in the tree. + * + * @return number of value nodes currently in the tree. + */ + size_t size() const; + + /** + * Swap the content of the tree with another instance. + * + * @param other another instance to swap the content with. + */ + void swap(rtree& other); + + /** + * Empty the entire container. + */ + void clear(); + + /** + * Walk down the entire tree depth first. + * + * @param func function or function object that gets called at each node in + * the tree. + */ + template + void walk(FuncT func) const; + + /** + * Check the integrity of the entire tree structure. + * + * @param props specify how the check is to be performed. + * + * @exception integrity_error if the integrity check fails. + */ + void check_integrity(const integrity_check_properties& props) const; + + /** + * Export the structure of a tree in textural format. + * + * @param mode specify the format in which to represent the structure of a + * tree. + * + * @return string representation of the tree structure. + */ + std::string export_tree(export_tree_type mode) const; + +private: + void insert_impl(const point_type& start, const point_type& end, value_type&& value); + void insert_impl(const point_type& start, const point_type& end, const value_type& value); + + void erase_impl(const node_store* ns, size_t depth); + + /** + * Build and return a callable function object that you can call in order + * to convert node's memory location to a normalized unique string still + * representative of that node. + */ + detail::rtree::ptr_to_string build_ptr_to_string_map() const; + + std::string export_tree_formatted() const; + std::string export_tree_extent_as_obj() const; + std::string export_tree_extent_as_svg() const; + + void insert(node_store&& ns, std::unordered_set* reinserted_depths); + void insert_dir(node_store&& ns, size_t max_depth); + + /** + * Split an overfilled node. The node to split is expected to have + * exactly M+1 child nodes where M is the maximum number of child nodes a + * single node is allowed to have. + * + * @param ns node to split. + */ + void split_node(node_store* ns); + + void perform_forced_reinsertion(node_store* ns, std::unordered_set& reinserted_depth); + + /** + * Determine the best dimension to split by, and sort the child nodes by + * that dimension. + * + * @param children child nodes to sort. + */ + void sort_dir_store_by_split_dimension(dir_store_type& children); + + void sort_dir_store_by_dimension(size_t dim, dir_store_type& children); + + size_t pick_optimal_distribution(dir_store_type& children) const; + + insertion_point find_leaf_directory_node_for_insertion(const extent_type& bb); + node_store* find_nonleaf_directory_node_for_insertion(const extent_type& bb, size_t max_depth); + + template + void descend_with_func(FuncT func) const; + + using search_condition_type = std::function; + + template + void search_descend( + size_t depth, const search_condition_type& dir_cond, const search_condition_type& value_cond, + typename _ResT::node_store_type& ns, _ResT& results) const; + + void shrink_tree_upward(node_store* ns, const extent_type& bb_affected); + +private: + node_store m_root; +}; + +} // namespace mdds + +#include "rtree_def.inl" + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/rtree_def.inl b/include/mdds/rtree_def.inl new file mode 100644 index 0000000..d7c4fa7 --- /dev/null +++ b/include/mdds/rtree_def.inl @@ -0,0 +1,2625 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2018 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#include "mdds/global.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace mdds { + +namespace detail { namespace rtree { + +template +using remove_cvref_t = typename std::remove_cv::type>::type; + +inline const char* to_string(node_type nt) +{ + switch (nt) + { + case node_type::unspecified: + return "unspecified"; + case node_type::directory_leaf: + return "directory-leaf"; + case node_type::directory_nonleaf: + return "directory-nonleaf"; + case node_type::value: + return "value"; + } + + return "???"; +} + +inline size_t calc_optimal_segment_size_for_pack(size_t init_size, size_t min_size, size_t max_size, size_t value_count) +{ + // Keep increasing the size until the remainder becomes at least the min size. + size_t final_size = init_size; + for (; final_size < max_size; ++final_size) + { + size_t mod = value_count % final_size; + if (!mod || mod >= min_size) + return final_size; + } + + // If increasing the size doesn't work, then decrease it. + final_size = init_size; + for (--final_size; min_size < final_size; --final_size) + { + size_t mod = value_count % final_size; + if (!mod || mod >= min_size) + return final_size; + } + + // Nothing has worked. Use the original value. + return init_size; +} + +template +auto calc_linear_intersection(size_t dim, const Extent& bb1, const Extent& bb2) + -> remove_cvref_t +{ + using key_type = remove_cvref_t; + + key_type start1 = bb1.start.d[dim], end1 = bb1.end.d[dim]; + key_type start2 = bb2.start.d[dim], end2 = bb2.end.d[dim]; + + // Ensure that start1 < start2. + + if (start1 > start2) + { + std::swap(start1, start2); + std::swap(end1, end2); + } + + assert(start1 <= start2); + + if (end1 < start2) + { + // 1 : |------| + // 2 : |-------| + + // These two are not intersected at all. Bail out. + return key_type(); + } + + if (end1 < end2) + { + // 1 : |---------| + // 2 : |----------| + + return end1 - start2; + } + + // 1 : |--------------| + // 2 : |-----| + + return end2 - start2; +} + +template +bool intersects(const Extent& bb1, const Extent& bb2) +{ + constexpr size_t dim_size = sizeof(bb1.start.d) / sizeof(bb1.start.d[0]); + using key_type = remove_cvref_t; + + for (size_t dim = 0; dim < dim_size; ++dim) + { + key_type start1 = bb1.start.d[dim], end1 = bb1.end.d[dim]; + key_type start2 = bb2.start.d[dim], end2 = bb2.end.d[dim]; + + // Ensure that start1 <= start2. + + if (start1 > start2) + { + std::swap(start1, start2); + std::swap(end1, end2); + } + + assert(start1 <= start2); + + if (end1 < start2) + { + // 1 : |------| + // 2 : |-------| + + // These two are not intersected at all. Bail out. + return false; + } + } + + return true; +} + +template +auto calc_intersection(const Extent& bb1, const Extent& bb2) -> remove_cvref_t +{ + constexpr size_t dim_size = sizeof(bb1.start.d) / sizeof(bb1.start.d[0]); + static_assert(dim_size > 0, "Dimension cannot be zero."); + using key_type = remove_cvref_t; + + key_type total_volume = calc_linear_intersection(0, bb1, bb2); + if (!total_volume) + return key_type(); + + for (size_t dim = 1; dim < dim_size; ++dim) + { + key_type segment_len = calc_linear_intersection(dim, bb1, bb2); + if (!segment_len) + return key_type(); + + total_volume *= segment_len; + } + + return total_volume; +} + +template +bool enlarge_extent_to_fit(Extent& parent, const Extent& child) +{ + constexpr size_t dim_size = sizeof(parent.start.d) / sizeof(parent.start.d[0]); + static_assert(dim_size > 0, "Dimension cannot be zero."); + bool enlarged = false; + + for (size_t dim = 0; dim < dim_size; ++dim) + { + if (child.start.d[dim] < parent.start.d[dim]) + { + parent.start.d[dim] = child.start.d[dim]; + enlarged = true; + } + + if (parent.end.d[dim] < child.end.d[dim]) + { + parent.end.d[dim] = child.end.d[dim]; + enlarged = true; + } + } + + return enlarged; +} + +template +auto calc_area(const Extent& bb) -> remove_cvref_t +{ + constexpr size_t dim_size = sizeof(bb.start.d) / sizeof(bb.start.d[0]); + static_assert(dim_size > 0, "Dimension cannot be zero."); + using key_type = remove_cvref_t; + + key_type area = bb.end.d[0] - bb.start.d[0]; + for (size_t dim = 1; dim < dim_size; ++dim) + area *= bb.end.d[dim] - bb.start.d[dim]; + + return area; +} + +template +auto calc_square_distance(const Pt& pt1, const Pt& pt2) -> remove_cvref_t +{ + constexpr size_t dim_size = sizeof(pt1.d) / sizeof(pt1.d[0]); + static_assert(dim_size > 0, "Dimension cannot be zero."); + using key_type = remove_cvref_t; + + key_type dist = key_type(); + for (size_t dim = 0; dim < dim_size; ++dim) + { + key_type v1 = pt1.d[dim], v2 = pt2.d[dim]; + + if (v1 > v2) + std::swap(v1, v2); // ensure that v1 <= v2. + + assert(v1 <= v2); + + key_type diff = v2 - v1; + dist += diff * diff; + } + + return dist; +} + +/** + * The margin here is defined as the sum of the lengths of the edges of a + * bounding box, per the original paper on R*-tree. It's half-margin + * because it only adds one of the two edges in each dimension. + */ +template +auto calc_half_margin(const Extent& bb) -> remove_cvref_t +{ + constexpr size_t dim_size = sizeof(bb.start.d) / sizeof(bb.start.d[0]); + static_assert(dim_size > 0, "Dimension cannot be zero."); + using key_type = remove_cvref_t; + + key_type margin = bb.end.d[0] - bb.start.d[0]; + for (size_t dim = 1; dim < dim_size; ++dim) + margin += bb.end.d[dim] - bb.start.d[dim]; + + return margin; +} + +/** + * Area enlargement is calculated by calculating the area of the enlarged + * box subtracted by the area of the original box prior to the enlargement. + * + * @param bb_host bounding box of the area receiving the new object. + * @param bb_guest bounding of the new object being inserted. + * + * @return quantity of the area enlargement. + */ +template +auto calc_area_enlargement(const Extent& bb_host, const Extent& bb_guest) + -> remove_cvref_t +{ + constexpr size_t dim_size = sizeof(bb_host.start.d) / sizeof(bb_host.start.d[0]); + static_assert(dim_size > 0, "Dimension cannot be zero."); + using key_type = remove_cvref_t; + using extent = Extent; + + // Calculate the original area. + key_type original_area = calc_area(bb_host); + + // Enlarge the box for the new object if needed. + extent bb_host_enlarged = bb_host; // make a copy. + bool enlarged = enlarge_extent_to_fit(bb_host_enlarged, bb_guest); + if (!enlarged) + // Area enlargement did not take place. + return key_type(); + + key_type enlarged_area = calc_area(bb_host_enlarged); + + return enlarged_area - original_area; +} + +template +auto calc_extent(Iter it, Iter it_end) -> decltype(it->extent) +{ + auto bb = it->extent; + for (++it; it != it_end; ++it) + enlarge_extent_to_fit(bb, it->extent); + + return bb; +} + +template +auto get_center_point(const Extent& extent) -> decltype(extent.start) +{ + constexpr size_t dim_size = sizeof(extent.start.d) / sizeof(extent.start.d[0]); + static_assert(dim_size > 0, "Dimension cannot be zero."); + using point_type = decltype(extent.start); + using key_type = decltype(extent.start.d[0]); + + point_type ret; + + static const key_type two = 2; + + for (size_t dim = 0; dim < dim_size; ++dim) + ret.d[dim] = (extent.end.d[dim] + extent.start.d[dim]) / two; + + return ret; +} + +template +struct min_value_pos +{ + KeyT value = KeyT(); + size_t pos = 0; + size_t count = 0; + + void assign(KeyT new_value, size_t new_pos) + { + if (count) + { + // Assign only if it's less than the current value. + if (new_value < value) + { + value = new_value; + pos = new_pos; + } + } + else + { + // The very first value. Just take it. + value = new_value; + pos = new_pos; + } + + ++count; + } +}; + +template +struct reinsertion_bucket +{ + using key_type = KeyT; + + key_type distance; + size_t src_pos; +}; + +template +struct ptr_to_string +{ + using node_ptr_type = _NodePtrT; + using node_ptr_map_type = std::unordered_map; + + node_ptr_map_type node_ptr_map; + + std::string operator()(node_ptr_type np) const + { + auto it = node_ptr_map.find(np); + return (it == node_ptr_map.end()) ? "(*, *)" : it->second; + } + + ptr_to_string() + { + static_assert(std::is_pointer::value, "Node pointer type must be a real pointer type."); + } + + ptr_to_string(const ptr_to_string&) = delete; + ptr_to_string(ptr_to_string&& other) : node_ptr_map(std::move(other.node_ptr_map)) + {} +}; + +}} // namespace detail::rtree + +template +rtree::point_type::point_type() +{ + // Initialize the point values with the key type's default value. + key_type* p = d; + key_type* p_end = p + traits_type::dimensions; + + for (; p != p_end; ++p) + *p = key_type{}; +} + +template +rtree::point_type::point_type(std::initializer_list vs) +{ + // Initialize the point values with the key type's default value. + key_type* dst = d; + key_type* dst_end = dst + traits_type::dimensions; + + for (const key_type& v : vs) + { + if (dst == dst_end) + throw std::range_error("number of elements exceeds the dimension size."); + + *dst = v; + ++dst; + } +} + +template +std::string rtree::point_type::to_string() const +{ + std::ostringstream os; + + os << "("; + for (size_t i = 0; i < traits_type::dimensions; ++i) + { + if (i > 0) + os << ", "; + os << d[i]; + } + os << ")"; + + return os.str(); +} + +template +bool rtree::point_type::operator==(const point_type& other) const +{ + const key_type* left = d; + const key_type* right = other.d; + const key_type* left_end = left + traits_type::dimensions; + + for (; left != left_end; ++left, ++right) + { + if (*left != *right) + return false; + } + + return true; +} + +template +bool rtree::point_type::operator!=(const point_type& other) const +{ + return !operator==(other); +} + +template +rtree::extent_type::extent_type() +{} + +template +rtree::extent_type::extent_type(const point_type& _start, const point_type& _end) + : start(_start), end(_end) +{} + +template +std::string rtree::extent_type::to_string() const +{ + std::ostringstream os; + os << start.to_string(); + + if (!is_point()) + os << " - " << end.to_string(); + + return os.str(); +} + +template +bool rtree::extent_type::is_point() const +{ + return start == end; +} + +template +bool rtree::extent_type::operator==(const extent_type& other) const +{ + return start == other.start && end == other.end; +} + +template +bool rtree::extent_type::operator!=(const extent_type& other) const +{ + return !operator==(other); +} + +template +bool rtree::extent_type::contains(const point_type& pt) const +{ + for (size_t dim = 0; dim < traits_type::dimensions; ++dim) + { + if (pt.d[dim] < start.d[dim] || end.d[dim] < pt.d[dim]) + return false; + } + + return true; +} + +template +bool rtree::extent_type::contains(const extent_type& bb) const +{ + for (size_t dim = 0; dim < traits_type::dimensions; ++dim) + { + if (bb.start.d[dim] < start.d[dim] || end.d[dim] < bb.end.d[dim]) + return false; + } + + return true; +} + +template +bool rtree::extent_type::intersects(const extent_type& bb) const +{ + return detail::rtree::intersects(bb, *this); +} + +template +bool rtree::extent_type::contains_at_boundary(const extent_type& bb) const +{ + for (size_t dim = 0; dim < traits_type::dimensions; ++dim) + { + if (start.d[dim] == bb.start.d[dim] || bb.end.d[dim] == end.d[dim]) + return true; + } + + return false; +} + +template +rtree::node_store::node_store() + : type(node_type::unspecified), parent(nullptr), node_ptr(nullptr), count(0), valid_pointer(true) +{} + +template +rtree::node_store::node_store(node_store&& r) + : type(r.type), extent(r.extent), parent(r.parent), node_ptr(r.node_ptr), count(r.count), + valid_pointer(r.valid_pointer) +{ + r.type = node_type::unspecified; + r.extent = extent_type(); + r.parent = nullptr; + r.node_ptr = nullptr; + r.count = 0; + r.valid_pointer = true; +} + +template +rtree::node_store::node_store(node_type _type, const extent_type& _extent, node* _node_ptr) + : type(_type), extent(_extent), parent(nullptr), node_ptr(_node_ptr), count(0), valid_pointer(true) +{} + +template +rtree::node_store::~node_store() +{ + if (node_ptr) + { + switch (type) + { + case node_type::directory_leaf: + case node_type::directory_nonleaf: + delete static_cast(node_ptr); + break; + case node_type::value: + delete static_cast(node_ptr); + break; + case node_type::unspecified: + default: + assert(!"node::~node: unknown node type!"); + } + } +} + +template +typename rtree::node_store rtree::node_store::clone() const +{ + auto func_copy_dir = [this](node_store& cloned, const directory_node* src) { + directory_node* dir = cloned.get_directory_node(); + assert(dir); + for (const node_store& ns : src->children) + dir->children.push_back(ns.clone()); + + cloned.count = count; + cloned.extent = extent; + }; + + switch (type) + { + case node_type::directory_leaf: + { + const directory_node* src = static_cast(node_ptr); + node_store cloned = create_leaf_directory_node(); + func_copy_dir(cloned, src); + return cloned; + } + case node_type::directory_nonleaf: + { + const directory_node* src = static_cast(node_ptr); + node_store cloned = create_nonleaf_directory_node(); + func_copy_dir(cloned, src); + return cloned; + } + case node_type::value: + { + const value_node* vn = static_cast(node_ptr); + return create_value_node(extent, vn->value); + } + case node_type::unspecified: + default: + assert(!"node::~node: unknown node type!"); + } +} + +template +typename rtree::node_store rtree::node_store::create_leaf_directory_node() +{ + node_store ret(node_type::directory_leaf, extent_type(), new directory_node); + ret.valid_pointer = false; + return ret; +} + +template +typename rtree::node_store rtree< + KeyT, ValueT, Traits>::node_store::create_nonleaf_directory_node() +{ + node_store ret(node_type::directory_nonleaf, extent_type(), new directory_node); + ret.valid_pointer = false; + return ret; +} + +template +typename rtree::node_store rtree::node_store::create_value_node( + const extent_type& extent, value_type&& v) +{ + node_store ret(node_type::value, extent, new value_node(std::move(v))); + return ret; +} + +template +typename rtree::node_store rtree::node_store::create_value_node( + const extent_type& extent, const value_type& v) +{ + node_store ret(node_type::value, extent, new value_node(v)); + return ret; +} + +template +typename rtree::node_store& rtree::node_store::operator=(node_store&& other) +{ + node_store tmp(std::move(other)); + swap(tmp); + return *this; +} + +template +bool rtree::node_store::pack() +{ + const directory_node* dir = get_directory_node(); + if (!dir) + return false; + + const dir_store_type& children = dir->children; + if (children.empty()) + { + // This node has no children. Reset the bounding box to empty. + extent_type new_box; + bool changed = new_box != extent; + extent = new_box; + return changed; + } + + extent_type new_box = dir->calc_extent(); + bool changed = new_box != extent; + extent = new_box; // update the bounding box. + return changed; +} + +template +void rtree::node_store::pack_upward() +{ + bool propagate = true; + for (node_store* p = parent; propagate && p; p = p->parent) + propagate = p->pack(); +} + +template +bool rtree::node_store::is_directory() const +{ + switch (type) + { + case node_type::directory_leaf: + case node_type::directory_nonleaf: + return true; + default:; + } + + return false; +} + +template +bool rtree::node_store::is_root() const +{ + return parent == nullptr; +} + +template +bool rtree::node_store::exceeds_capacity() const +{ + if (type != node_type::directory_leaf) + return false; + + return count > traits_type::max_node_size; +} + +template +void rtree::node_store::swap(node_store& other) +{ + std::swap(type, other.type); + std::swap(extent, other.extent); + std::swap(parent, other.parent); + std::swap(node_ptr, other.node_ptr); + std::swap(count, other.count); + std::swap(valid_pointer, other.valid_pointer); +} + +template +void rtree::node_store::reset_parent_pointers_of_children() +{ + if (valid_pointer) + return; + + directory_node* dir = get_directory_node(); + if (!dir) + return; + + for (node_store& ns : dir->children) + { + ns.parent = this; + ns.reset_parent_pointers_of_children(); + } + + valid_pointer = true; +} + +template +void rtree::node_store::reset_parent_pointers() +{ + valid_pointer = false; + reset_parent_pointers_of_children(); +} + +template +typename rtree::directory_node* rtree::node_store::get_directory_node() +{ + if (!is_directory()) + return nullptr; + + return static_cast(node_ptr); +} + +template +const typename rtree::directory_node* rtree< + KeyT, ValueT, Traits>::node_store::get_directory_node() const +{ + if (!is_directory()) + return nullptr; + + return static_cast(node_ptr); +} + +template +bool rtree::node_store::erase_child(const node_store* p) +{ + if (!is_directory()) + return false; + + directory_node* dir = static_cast(node_ptr); + bool erased = dir->erase(p); + if (erased) + --count; + + assert(count == dir->children.size()); + return erased; +} + +template +rtree::node::node() +{} + +template +rtree::node::~node() +{} + +template +rtree::value_node::value_node(value_type&& _value) : value(std::move(_value)) +{} + +template +rtree::value_node::value_node(const value_type& _value) : value(_value) +{} + +template +rtree::value_node::~value_node() +{} + +template +rtree::directory_node::directory_node() +{} + +template +rtree::directory_node::~directory_node() +{} + +template +bool rtree::directory_node::erase(const node_store* ns) +{ + auto it = std::find_if( + children.begin(), children.end(), [ns](const node_store& this_ns) -> bool { return &this_ns == ns; }); + + if (it == children.end()) + return false; + + // NB: std::deque::erase invalidates all elements when the erased element + // is somwhere in the middle. But if the erased element is either the + // first or the last element, only the erased element becomes invalidated. + + std::size_t pos = std::distance(children.begin(), it); + bool all_valid = pos == 0 || pos == children.size() - 1; + + it = children.erase(it); + + if (!all_valid) + { + for (node_store& this_ns : children) + this_ns.valid_pointer = false; + } + + return true; +} + +template +typename rtree::node_store* rtree< + KeyT, ValueT, Traits>::directory_node::get_child_with_minimal_overlap(const extent_type& bb) +{ + key_type min_overlap = key_type(); + key_type min_area_enlargement = key_type(); + key_type min_area = key_type(); + + node_store* dst = nullptr; + + for (node_store& ns : children) + { + directory_node* dir = static_cast(ns.node_ptr); + key_type overlap = dir->calc_overlap_cost(bb); + key_type area_enlargement = detail::rtree::calc_area_enlargement(ns.extent, bb); + key_type area = detail::rtree::calc_area(ns.extent); + + bool pick_this = false; + + if (!dst) + pick_this = true; + else if (overlap < min_overlap) + // Pick the entry with the smaller overlap cost increase. + pick_this = true; + else if (area_enlargement < min_area_enlargement) + // Pick the entry with the smaller area enlargment. + pick_this = true; + else if (area < min_area) + // Resolve ties by picking the one with on the smaller area + // rectangle. + pick_this = true; + + if (pick_this) + { + min_overlap = overlap; + min_area_enlargement = area_enlargement; + min_area = area; + dst = &ns; + } + } + + return dst; +} + +template +typename rtree::node_store* rtree< + KeyT, ValueT, Traits>::directory_node::get_child_with_minimal_area_enlargement(const extent_type& bb) +{ + // Compare the costs of area enlargements. + key_type min_cost = key_type(); + key_type min_area = key_type(); + + node_store* dst = nullptr; + + for (node_store& ns : children) + { + key_type cost = detail::rtree::calc_area_enlargement(ns.extent, bb); + key_type area = detail::rtree::calc_area(ns.extent); + + bool pick_this = false; + + if (!dst) + pick_this = true; + else if (cost < min_cost) + // Pick the entry with the smaller area enlargment. + pick_this = true; + else if (area < min_area) + // Resolve ties by picking the one with on the smaller area + // rectangle. + pick_this = true; + + if (pick_this) + { + min_cost = cost; + min_area = area; + dst = &ns; + } + } + + return dst; +} + +template +typename rtree::extent_type rtree::directory_node::calc_extent() const +{ + auto it = children.cbegin(), ite = children.cend(); + + extent_type box; + if (it != ite) + box = detail::rtree::calc_extent(it, ite); + + return box; +} + +template +typename rtree::key_type rtree::directory_node::calc_overlap_cost( + const extent_type& bb) const +{ + key_type overlap_cost = key_type(); + + for (const node_store& ns : children) + overlap_cost += detail::rtree::calc_intersection(ns.extent, bb); + + return overlap_cost; +} + +template +bool rtree::directory_node::has_leaf_directory() const +{ + for (const auto& ns : children) + { + if (ns.type == node_type::directory_leaf) + return true; + } + + return false; +} + +template +template +void rtree::search_results_base::add_node_store(node_store_type* ns, size_t depth) +{ + m_store.emplace_back(ns, depth); +} + +template +template +rtree::search_results_base::entry::entry(node_store_type* _ns, size_t _depth) + : ns(_ns), depth(_depth) +{} + +template +template +rtree::iterator_base<_SelfIter, _StoreIter, _ValueT>::iterator_base(store_iterator_type pos) + : m_pos(std::move(pos)) +{} + +template +template +bool rtree::iterator_base<_SelfIter, _StoreIter, _ValueT>::operator==( + const self_iterator_type& other) const +{ + return m_pos == other.m_pos; +} + +template +template +bool rtree::iterator_base<_SelfIter, _StoreIter, _ValueT>::operator!=( + const self_iterator_type& other) const +{ + return !operator==(other); +} + +template +template +typename rtree::template iterator_base<_SelfIter, _StoreIter, _ValueT>::self_iterator_type& rtree< + KeyT, ValueT, Traits>::iterator_base<_SelfIter, _StoreIter, _ValueT>::operator++() +{ + ++m_pos; + return static_cast(*this); +} + +template +template +typename rtree::template iterator_base<_SelfIter, _StoreIter, _ValueT>::self_iterator_type rtree< + KeyT, ValueT, Traits>::iterator_base<_SelfIter, _StoreIter, _ValueT>::operator++(int) +{ + self_iterator_type ret(m_pos); + ++m_pos; + return ret; +} + +template +template +typename rtree::template iterator_base<_SelfIter, _StoreIter, _ValueT>::self_iterator_type& rtree< + KeyT, ValueT, Traits>::iterator_base<_SelfIter, _StoreIter, _ValueT>::operator--() +{ + --m_pos; + return static_cast(*this); +} + +template +template +typename rtree::template iterator_base<_SelfIter, _StoreIter, _ValueT>::self_iterator_type rtree< + KeyT, ValueT, Traits>::iterator_base<_SelfIter, _StoreIter, _ValueT>::operator--(int) +{ + self_iterator_type ret(m_pos); + --m_pos; + return ret; +} + +template +template +const typename rtree::extent_type& rtree::iterator_base< + _SelfIter, _StoreIter, _ValueT>::extent() const +{ + return m_pos->ns->extent; +} + +template +template +size_t rtree::iterator_base<_SelfIter, _StoreIter, _ValueT>::depth() const +{ + return m_pos->depth; +} + +template +typename rtree::const_iterator rtree::const_search_results::cbegin() const +{ + return const_iterator(m_store.cbegin()); +} + +template +typename rtree::const_iterator rtree::const_search_results::cend() const +{ + return const_iterator(m_store.cend()); +} + +template +typename rtree::const_iterator rtree::const_search_results::begin() const +{ + return const_iterator(m_store.cbegin()); +} + +template +typename rtree::const_iterator rtree::const_search_results::end() const +{ + return const_iterator(m_store.cend()); +} + +template +typename rtree::iterator rtree::search_results::begin() +{ + return iterator(m_store.begin()); +} + +template +typename rtree::iterator rtree::search_results::end() +{ + return iterator(m_store.end()); +} + +template +rtree::const_iterator::const_iterator(store_iterator_type pos) : base_type(std::move(pos)) +{} + +template +rtree::iterator::iterator(store_iterator_type pos) : base_type(std::move(pos)) +{} + +template +rtree::bulk_loader::bulk_loader() +{} + +template +void rtree::bulk_loader::insert(const extent_type& extent, value_type&& value) +{ + insert_impl(extent, std::move(value)); +} + +template +void rtree::bulk_loader::insert(const extent_type& extent, const value_type& value) +{ + insert_impl(extent, value); +} + +template +void rtree::bulk_loader::insert(const point_type& position, value_type&& value) +{ + insert_impl(extent_type({position, position}), std::move(value)); +} + +template +void rtree::bulk_loader::insert(const point_type& position, const value_type& value) +{ + insert_impl(extent_type({position, position}), value); +} + +template +void rtree::bulk_loader::insert_impl(const extent_type& extent, value_type&& value) +{ + node_store ns_value = node_store::create_value_node(extent, std::move(value)); + m_store.emplace_back(std::move(ns_value)); +} + +template +void rtree::bulk_loader::insert_impl(const extent_type& extent, const value_type& value) +{ + node_store ns_value = node_store::create_value_node(extent, value); + m_store.emplace_back(std::move(ns_value)); +} + +template +rtree rtree::bulk_loader::pack() +{ + size_t depth = 0; + for (; m_store.size() > traits_type::max_node_size; ++depth) + pack_level(m_store, depth); + + // By this point, the number of directory nodes should have been reduced + // below the max node size. Create a root directory and store them there. + + assert(m_store.size() <= traits_type::max_node_size); + + node_store root = node_store::create_leaf_directory_node(); + if (depth > 0) + root.type = node_type::directory_nonleaf; + + directory_node* dir = root.get_directory_node(); + assert(dir); + dir->children.swap(m_store); + + root.count = dir->children.size(); + root.pack(); + + rtree tree(std::move(root)); + return tree; +} + +template +void rtree::bulk_loader::pack_level(dir_store_type& store, size_t depth) +{ + assert(!store.empty()); + + float n_total_node = std::ceil(store.size() / float(traits_type::max_node_size)); + float n_splits_per_dim = std::ceil(std::pow(n_total_node, 1.0f / float(traits_type::dimensions))); + + // The first dimension will start with one segment. + std::vector segments; + segments.emplace_back(store.begin(), store.end(), store.size()); + + for (size_t dim = 0; dim < traits_type::dimensions; ++dim) + { + if (segments[0].size <= traits_type::max_node_size) + break; + + std::vector next_segments; + + for (dir_store_segment& seg : segments) + { + assert(seg.size == size_t(std::distance(seg.begin, seg.end))); + + if (seg.size <= traits_type::max_node_size) + { + next_segments.push_back(seg); + continue; + } + + // Sort by the current dimension key. + std::sort(seg.begin, seg.end, [dim](const node_store& left, const node_store& right) -> bool { + // Compare the middle points. + float left_key = (left.extent.end.d[dim] + left.extent.start.d[dim]) / 2.0f; + float right_key = (right.extent.end.d[dim] + right.extent.start.d[dim]) / 2.0f; + + return left_key < right_key; + }); + + // Size of each segment in this dimension splits. + size_t segment_size = detail::rtree::calc_optimal_segment_size_for_pack( + std::ceil(seg.size / n_splits_per_dim), traits_type::min_node_size, traits_type::max_node_size, + seg.size); + + size_t n_cur_segment = 0; + auto begin = seg.begin; + for (auto it = begin; it != seg.end; ++it, ++n_cur_segment) + { + if (n_cur_segment == segment_size) + { + // Push a new segment. + next_segments.emplace_back(begin, it, n_cur_segment); + begin = it; + n_cur_segment = 0; + } + } + + if (begin != seg.end) + { + size_t n = std::distance(begin, seg.end); + next_segments.emplace_back(begin, seg.end, n); + } + } + +#ifdef MDDS_RTREE_DEBUG + size_t test_total = 0; + for (const auto& seg : next_segments) + test_total += seg.size; + + if (test_total != store.size()) + throw std::logic_error("The total combined segment sizes must equal the size of the inserted values!"); +#endif + segments.swap(next_segments); + } + +#ifdef MDDS_RTREE_DEBUG + // Check the final segment. + size_t test_total = 0; + for (const auto& seg : segments) + test_total += seg.size; + + if (test_total != store.size()) + throw std::logic_error("The total combined segment sizes must equal the size of the inserted values!"); +#endif + + assert(!segments.empty()); + + // Create a set of directory nodes from the current segments. + dir_store_type next_store; + for (dir_store_segment& seg : segments) + { + node_store ns = node_store::create_leaf_directory_node(); + if (depth > 0) + ns.type = node_type::directory_nonleaf; + + directory_node* dir = ns.get_directory_node(); + assert(dir); // this better not be null since we know it's a directory node. + + for (auto it = seg.begin; it != seg.end; ++it) + dir->children.push_back(std::move(*it)); + + ns.count = dir->children.size(); + ns.pack(); + next_store.push_back(std::move(ns)); + } + + store.swap(next_store); +} + +template +rtree::rtree() : m_root(node_store::create_leaf_directory_node()) +{ + static_assert( + traits_type::min_node_size <= traits_type::max_node_size / 2, + "Minimum node size must be less than half of the maximum node size."); + + static_assert( + traits_type::reinsertion_size <= (traits_type::max_node_size - traits_type::min_node_size + 1), + "Reinsertion size is too large."); +} + +template +rtree::rtree(rtree&& other) : m_root(std::move(other.m_root)) +{ + // The root node must be a valid directory at all times. + other.m_root = node_store::create_leaf_directory_node(); + + // Since the moved root has its memory location changed, we need to update + // the parent pointers in its child nodes. + m_root.reset_parent_pointers(); +} + +template +rtree::rtree(const rtree& other) : m_root(other.m_root.clone()) +{ + m_root.reset_parent_pointers(); +} + +template +rtree::rtree(node_store&& root) : m_root(std::move(root)) +{ + m_root.reset_parent_pointers(); +} + +template +rtree::~rtree() +{} + +template +rtree& rtree::operator=(const rtree& other) +{ + rtree tmp(other); + tmp.swap(*this); + return *this; +} + +template +rtree& rtree::operator=(rtree&& other) +{ + rtree tmp(std::move(other)); + tmp.swap(*this); + return *this; +} + +template +void rtree::insert(const extent_type& extent, value_type&& value) +{ + insert_impl(extent.start, extent.end, std::move(value)); +} + +template +void rtree::insert(const extent_type& extent, const value_type& value) +{ + insert_impl(extent.start, extent.end, value); +} + +template +void rtree::insert(const point_type& position, value_type&& value) +{ + insert_impl(position, position, std::move(value)); +} + +template +void rtree::insert(const point_type& position, const value_type& value) +{ + insert_impl(position, position, value); +} + +template +void rtree::insert_impl(const point_type& start, const point_type& end, value_type&& value) +{ + extent_type bb(start, end); + node_store new_ns = node_store::create_value_node(bb, std::move(value)); + + std::unordered_set reinserted_depths; + insert(std::move(new_ns), &reinserted_depths); +} + +template +void rtree::insert_impl(const point_type& start, const point_type& end, const value_type& value) +{ + extent_type bb(start, end); + node_store new_ns = node_store::create_value_node(bb, value); + + std::unordered_set reinserted_depths; + insert(std::move(new_ns), &reinserted_depths); +} + +template +void rtree::insert(node_store&& ns, std::unordered_set* reinserted_depths) +{ + extent_type ns_box = ns.extent; + + insertion_point insert_pt = find_leaf_directory_node_for_insertion(ns_box); + node_store* dir_ns = insert_pt.ns; + size_t depth = insert_pt.depth; + + assert(dir_ns); + assert(dir_ns->type == node_type::directory_leaf); + directory_node* dir = static_cast(dir_ns->node_ptr); + + // Insert the new value to this node. + ns.parent = insert_pt.ns; + dir->children.push_back(std::move(ns)); + ++dir_ns->count; + + if (dir_ns->exceeds_capacity()) + { + if (traits_type::enable_forced_reinsertion) + { + if (reinserted_depths && !reinserted_depths->count(depth)) + { + // We perform forced re-insertion exactly once per depth level. + reinserted_depths->insert(depth); + perform_forced_reinsertion(dir_ns, *reinserted_depths); + } + else + split_node(dir_ns); + } + else + split_node(dir_ns); + + return; + } + + if (dir_ns->count == 1) + dir_ns->extent = ns_box; + else + detail::rtree::enlarge_extent_to_fit(dir_ns->extent, ns_box); + + extent_type bb = dir_ns->extent; // grab the parent bounding box. + + // Propagate the bounding box update up the tree all the way to the root. + for (dir_ns = dir_ns->parent; dir_ns; dir_ns = dir_ns->parent) + { + assert(dir_ns->count > 0); + detail::rtree::enlarge_extent_to_fit(dir_ns->extent, bb); + } +} + +template +void rtree::insert_dir(node_store&& ns, size_t max_depth) +{ + assert(ns.is_directory()); + extent_type ns_box = ns.extent; + node_store* dir_ns = find_nonleaf_directory_node_for_insertion(ns_box, max_depth); + assert(dir_ns); + assert(dir_ns->type == node_type::directory_nonleaf); + directory_node* dir = static_cast(dir_ns->node_ptr); + + // Insert the new directory to this node. + ns.parent = dir_ns; + ns.valid_pointer = false; + dir->children.push_back(std::move(ns)); + ++dir_ns->count; + dir->children.back().reset_parent_pointers_of_children(); + + if (dir_ns->exceeds_capacity()) + { + split_node(dir_ns); + return; + } + + if (dir_ns->count == 1) + dir_ns->extent = ns_box; + else + detail::rtree::enlarge_extent_to_fit(dir_ns->extent, ns_box); + + extent_type bb = dir_ns->extent; // grab the parent bounding box. + + // Propagate the bounding box update up the tree all the way to the root. + for (dir_ns = dir_ns->parent; dir_ns; dir_ns = dir_ns->parent) + { + assert(dir_ns->count > 0); + detail::rtree::enlarge_extent_to_fit(dir_ns->extent, bb); + } +} + +template +typename rtree::const_search_results rtree::search( + const point_type& pt, search_type st) const +{ + search_condition_type dir_cond, value_cond; + + switch (st) + { + case search_type::overlap: + { + dir_cond = [&pt](const node_store& ns) -> bool { return ns.extent.contains(pt); }; + + value_cond = dir_cond; + break; + } + case search_type::match: + { + dir_cond = [&pt](const node_store& ns) -> bool { return ns.extent.contains(pt); }; + + value_cond = [&pt](const node_store& ns) -> bool { return ns.extent.start == pt && ns.extent.end == pt; }; + + break; + } + default: + throw std::runtime_error("Unhandled search type."); + } + + const_search_results ret; + search_descend(0, dir_cond, value_cond, m_root, ret); + return ret; +} + +template +typename rtree::search_results rtree::search( + const point_type& pt, search_type st) +{ + search_condition_type dir_cond, value_cond; + + switch (st) + { + case search_type::overlap: + { + dir_cond = [&pt](const node_store& ns) -> bool { return ns.extent.contains(pt); }; + + value_cond = dir_cond; + break; + } + case search_type::match: + { + dir_cond = [&pt](const node_store& ns) -> bool { return ns.extent.contains(pt); }; + + value_cond = [&pt](const node_store& ns) -> bool { return ns.extent.start == pt && ns.extent.end == pt; }; + + break; + } + default: + throw std::runtime_error("Unhandled search type."); + } + + search_results ret; + search_descend(0, dir_cond, value_cond, m_root, ret); + return ret; +} + +template +typename rtree::const_search_results rtree::search( + const extent_type& extent, search_type st) const +{ + search_condition_type dir_cond, value_cond; + + switch (st) + { + case search_type::overlap: + { + dir_cond = [&extent](const node_store& ns) -> bool { return ns.extent.intersects(extent); }; + + value_cond = dir_cond; + break; + } + case search_type::match: + { + dir_cond = [&extent](const node_store& ns) -> bool { return ns.extent.contains(extent); }; + + value_cond = [&extent](const node_store& ns) -> bool { return ns.extent == extent; }; + + break; + } + default: + throw std::runtime_error("Unhandled search type."); + } + + const_search_results ret; + search_descend(0, dir_cond, value_cond, m_root, ret); + return ret; +} + +template +typename rtree::search_results rtree::search( + const extent_type& extent, search_type st) +{ + search_condition_type dir_cond, value_cond; + + switch (st) + { + case search_type::overlap: + { + dir_cond = [&extent](const node_store& ns) -> bool { return ns.extent.intersects(extent); }; + + value_cond = dir_cond; + break; + } + case search_type::match: + { + dir_cond = [&extent](const node_store& ns) -> bool { return ns.extent.contains(extent); }; + + value_cond = [&extent](const node_store& ns) -> bool { return ns.extent == extent; }; + + break; + } + default: + throw std::runtime_error("Unhandled search type."); + } + + search_results ret; + search_descend(0, dir_cond, value_cond, m_root, ret); + return ret; +} + +template +void rtree::erase(const const_iterator& pos) +{ + const node_store* ns = pos.m_pos->ns; + size_t depth = pos.m_pos->depth; + erase_impl(ns, depth); +} + +template +void rtree::erase(const iterator& pos) +{ + const node_store* ns = pos.m_pos->ns; + size_t depth = pos.m_pos->depth; + erase_impl(ns, depth); +} + +template +void rtree::erase_impl(const node_store* ns, size_t depth) +{ + assert(ns->type == node_type::value); + assert(ns->parent); + + extent_type bb_erased = ns->extent; + + // Move up to the parent and find its stored location. + node_store* dir_ns = ns->parent; + --depth; + assert(dir_ns->type == node_type::directory_leaf); + bool erased = dir_ns->erase_child(ns); + assert(erased); + (void)erased; // to avoid getting a warning on "variable set but not used". + + if (dir_ns->is_root()) + { + shrink_tree_upward(dir_ns, bb_erased); + return; + } + + if (dir_ns->count >= traits_type::min_node_size) + { + // The parent directory still contains enough nodes. No need to dissolve it. + shrink_tree_upward(dir_ns, bb_erased); + return; + } + + // Dissolve the node the erased value node belongs to, and reinsert + // all its siblings. + + assert(!dir_ns->is_root()); + assert(dir_ns->count < traits_type::min_node_size); + + dir_store_type orphan_value_nodes; + directory_node* dir = static_cast(dir_ns->node_ptr); + dir->children.swap(orphan_value_nodes); // moves all the rest of the value node entries to the orphan store. + + // Move up one level, and remove this directory node from its parent directory node. + node_store* child_ns = dir_ns; + dir_ns = dir_ns->parent; + --depth; + erased = dir_ns->erase_child(child_ns); + assert(erased); + + dir_ns->reset_parent_pointers(); + dir_ns->pack(); + + orphan_node_entries_type orphan_dir_nodes; + + while (!dir_ns->is_root() && dir_ns->count < traits_type::min_node_size) + { + // This directory node is now underfilled. Move all its children out + // for re-insertion and dissolve this node. + dir = static_cast(dir_ns->node_ptr); + + while (!dir->children.empty()) + { + orphan_dir_nodes.emplace_back(); + orphan_dir_nodes.back().ns.swap(dir->children.back()); + orphan_dir_nodes.back().depth = depth + 1; // depth of the children. + dir->children.pop_back(); + } + + // Find and remove this node from its parent store. + node_store* dir_ns_child = dir_ns; + dir_ns = dir_ns->parent; + --depth; + erased = dir_ns->erase_child(dir_ns_child); + assert(erased); + dir_ns->reset_parent_pointers_of_children(); + dir_ns->pack(); + } + + while (!orphan_dir_nodes.empty()) + { + orphan_node_entry& entry = orphan_dir_nodes.back(); + insert_dir(std::move(entry.ns), entry.depth); + orphan_dir_nodes.pop_back(); + } + + while (!orphan_value_nodes.empty()) + { + insert(std::move(orphan_value_nodes.back()), nullptr); + orphan_value_nodes.pop_back(); + } + + if (m_root.count == 1) + { + // If the root node only has one child, make that child the new root. + // Be careful not to leak memory here... + + dir = static_cast(m_root.node_ptr); + assert(dir->children.size() == 1); + node_store new_root(std::move(dir->children.back())); + dir->children.clear(); + + new_root.parent = nullptr; + m_root.swap(new_root); + m_root.reset_parent_pointers(); + } +} + +template +const typename rtree::extent_type& rtree::extent() const +{ + return m_root.extent; +} + +template +bool rtree::empty() const +{ + return !m_root.count; +} + +template +size_t rtree::size() const +{ + size_t n = 0; + descend_with_func([&n](const node_properties& np) { + if (np.type == node_type::value) + ++n; + }); + + return n; +} + +template +void rtree::swap(rtree& other) +{ + m_root.swap(other.m_root); + m_root.reset_parent_pointers(); + other.m_root.reset_parent_pointers(); +} + +template +void rtree::clear() +{ + node_store new_root = node_store::create_leaf_directory_node(); + m_root.swap(new_root); +} + +template +template +void rtree::walk(FuncT func) const +{ + descend_with_func(std::move(func)); +} + +template +void rtree::check_integrity(const integrity_check_properties& props) const +{ + auto func_ptr_to_string = build_ptr_to_string_map(); + + switch (m_root.type) + { + case node_type::directory_leaf: + case node_type::directory_nonleaf: + // Good. + break; + default: + throw integrity_error("The root node must be a directory node."); + } + + if (m_root.parent) + throw integrity_error("The root node should not have a non-null parent."); + + std::vector ns_stack; + + std::function func_descend = [&ns_stack, &func_descend, &func_ptr_to_string, + &props](const node_store* ns, int level) -> bool { + bool valid = true; + + std::string indent; + for (int i = 0; i < level; ++i) + indent += " "; + + const node_store* parent = nullptr; + extent_type parent_bb; + if (!ns_stack.empty()) + { + parent = ns_stack.back(); + parent_bb = parent->extent; + } + + if (!props.throw_on_first_error) + { + std::cout << indent << "node: " << func_ptr_to_string(ns) << "; parent: " << func_ptr_to_string(ns->parent) + << "; type: " << to_string(ns->type) << "; extent: " << ns->extent.to_string() << std::endl; + } + + if (parent) + { + if (ns->parent != parent) + { + std::ostringstream os; + os << "The parent node pointer does not point to the real parent. (expected: " << parent + << "; stored in node: " << ns->parent << ")"; + if (props.throw_on_first_error) + throw integrity_error(os.str()); + std::cout << indent << "* " << os.str() << std::endl; + valid = false; + } + + if (!parent_bb.contains(ns->extent)) + { + std::ostringstream os; + os << "The extent of the child " << ns->extent.to_string() << " is not within the extent of the parent " + << parent_bb.to_string() << "."; + if (props.throw_on_first_error) + throw integrity_error(os.str()); + std::cout << indent << "* " << os.str() << std::endl; + valid = false; + } + + switch (ns->type) + { + case node_type::directory_leaf: + { + if (parent->type != node_type::directory_nonleaf) + { + std::ostringstream os; + os << "Parent of a leaf directory node must be non-leaf."; + if (props.throw_on_first_error) + throw integrity_error(os.str()); + std::cout << indent << "* " << os.str() << std::endl; + valid = false; + } + break; + } + case node_type::directory_nonleaf: + { + if (parent->type != node_type::directory_nonleaf) + { + std::ostringstream os; + os << "Parent of a non-leaf directory node must also be non-leaf."; + if (props.throw_on_first_error) + throw integrity_error(os.str()); + std::cout << indent << "* " << os.str() << std::endl; + valid = false; + } + break; + } + case node_type::value: + { + if (parent->type != node_type::directory_leaf) + { + std::ostringstream os; + os << "Parent of a value node must be a leaf directory node."; + if (props.throw_on_first_error) + throw integrity_error(os.str()); + std::cout << indent << "* " << os.str() << std::endl; + valid = false; + } + break; + } + default: + throw integrity_error("Unexpected node type!"); + } + } + + ns_stack.push_back(ns); + + switch (ns->type) + { + case node_type::directory_leaf: + case node_type::directory_nonleaf: + { + const directory_node* dir = static_cast(ns->node_ptr); + + if (ns->count != dir->children.size()) + { + std::ostringstream os; + os << "Incorrect count of child nodes detected. (expected: " << dir->children.size() + << "; actual: " << ns->count << ")"; + + if (props.throw_on_first_error) + throw integrity_error(os.str()); + + std::cout << indent << "* " << os.str() << std::endl; + valid = false; + } + + bool node_underfill_allowed = false; + if (ns->is_root() && ns->type == node_type::directory_leaf) + // If the root directory is a leaf, it's allowed to be underfilled. + node_underfill_allowed = true; + + if (!node_underfill_allowed && ns->count < traits_type::min_node_size) + { + std::ostringstream os; + os << "The number of child nodes (" << ns->count << ") is less than the minimum allowed number of " + << traits_type::min_node_size; + + if (props.error_on_min_node_size && props.throw_on_first_error) + throw integrity_error(os.str()); + + std::cout << indent << "* " << os.str() << std::endl; + + if (props.error_on_min_node_size) + valid = false; + } + + if (traits_type::max_node_size < ns->count) + { + std::ostringstream os; + os << "The number of child nodes (" << ns->count << ") exceeds the maximum allowed number of " + << traits_type::max_node_size; + + if (props.throw_on_first_error) + throw integrity_error(os.str()); + + std::cout << indent << "* " << os.str() << std::endl; + valid = false; + } + + // Check to make sure the bounding box of the current node is + // tightly packed. + extent_type bb_expected = dir->calc_extent(); + + if (bb_expected != ns->extent) + { + std::ostringstream os; + os << "The extent of the node " << ns->extent.to_string() << " does not equal truly tight extent " + << bb_expected.to_string(); + + if (props.throw_on_first_error) + throw integrity_error(os.str()); + + std::cout << indent << "* " << os.str() << std::endl; + valid = false; + } + + for (const node_store& ns_child : dir->children) + { + bool valid_subtree = func_descend(&ns_child, level + 1); + if (!valid_subtree) + valid = false; + } + + break; + } + case node_type::value: + // Do nothing. + break; + default: + throw integrity_error("Unexpected node type!"); + } + + ns_stack.pop_back(); + + return valid; + }; + + bool valid = func_descend(&m_root, 0); + + if (!valid) + throw integrity_error("Tree contains one or more errors."); +} + +template +std::string rtree::export_tree(export_tree_type mode) const +{ + switch (mode) + { + case export_tree_type::formatted_node_properties: + return export_tree_formatted(); + case export_tree_type::extent_as_obj: + return export_tree_extent_as_obj(); + case export_tree_type::extent_as_svg: + return export_tree_extent_as_svg(); + default: + throw std::runtime_error("unhandled export tree type."); + } +} + +template +detail::rtree::ptr_to_string::node_store*> rtree< + KeyT, ValueT, Traits>::build_ptr_to_string_map() const +{ + detail::rtree::ptr_to_string func; + + std::function func_build_node_ptr = + [&func_build_node_ptr, &func](const node_store* ns, int level, int pos) { + std::ostringstream os; + os << "(" << level << ", " << pos << ")"; + func.node_ptr_map.insert(std::make_pair(ns, os.str())); + + switch (ns->type) + { + case node_type::directory_leaf: + case node_type::directory_nonleaf: + { + const directory_node* dir = static_cast(ns->node_ptr); + + int child_pos = 0; + for (const node_store& ns_child : dir->children) + func_build_node_ptr(&ns_child, level + 1, child_pos++); + + break; + } + case node_type::value: + // Do nothing. + break; + default: + throw integrity_error("Unexpected node type!"); + } + }; + + func_build_node_ptr(&m_root, 0, 0); + + return func; +} + +template +std::string rtree::export_tree_formatted() const +{ + auto func_ptr_to_string = build_ptr_to_string_map(); + + std::ostringstream os; + + std::function func_descend = [&func_descend, &os, + &func_ptr_to_string](const node_store* ns, int level) { + std::string indent; + for (int i = 0; i < level; ++i) + indent += " "; + + os << indent << "node: " << func_ptr_to_string(ns) << "; parent: " << func_ptr_to_string(ns->parent) + << "; type: " << to_string(ns->type) << "; extent: " << ns->extent.to_string() << std::endl; + + switch (ns->type) + { + case node_type::directory_leaf: + case node_type::directory_nonleaf: + { + const directory_node* dir = static_cast(ns->node_ptr); + + for (const node_store& ns_child : dir->children) + func_descend(&ns_child, level + 1); + + break; + } + case node_type::value: + // Do nothing. + break; + default: + throw integrity_error("Unexpected node type!"); + } + }; + + func_descend(&m_root, 0); + + return os.str(); +} + +template +std::string rtree::export_tree_extent_as_obj() const +{ + if (traits_type::dimensions != 2u) + throw size_error("Only 2-dimensional trees are supported."); + + float unit_height = + ((m_root.extent.end.d[0] - m_root.extent.start.d[0]) + (m_root.extent.end.d[1] - m_root.extent.start.d[1])) / + 5.0f; + + // Calculate the width to use for point data. + float pt_width = std::min( + m_root.extent.end.d[0] - m_root.extent.start.d[0], m_root.extent.end.d[1] - m_root.extent.start.d[1]); + pt_width /= 400.0f; + pt_width = std::min(pt_width, 1.0f); + + std::ostringstream os; + size_t counter = 0; + + std::function func_descend = [&](const node_store* ns, int level) { + size_t offset = counter * 4; + point_type s = ns->extent.start; + point_type e = ns->extent.end; + if (s == e) + { + s.d[0] -= pt_width; + s.d[1] -= pt_width; + e.d[0] += pt_width; + e.d[1] += pt_width; + } + + os << "o extent " << counter << " (level " << level << ") " << s.to_string() << " - " << e.to_string() + << std::endl; + os << "v " << s.d[0] << ' ' << (level * unit_height) << ' ' << s.d[1] << std::endl; + os << "v " << s.d[0] << ' ' << (level * unit_height) << ' ' << e.d[1] << std::endl; + os << "v " << e.d[0] << ' ' << (level * unit_height) << ' ' << e.d[1] << std::endl; + os << "v " << e.d[0] << ' ' << (level * unit_height) << ' ' << s.d[1] << std::endl; + os << "f " << (offset + 1) << ' ' << (offset + 2) << ' ' << (offset + 3) << ' ' << (offset + 4) << std::endl; + + ++counter; + + switch (ns->type) + { + case node_type::directory_leaf: + case node_type::directory_nonleaf: + { + const directory_node* dir = static_cast(ns->node_ptr); + + for (const node_store& ns_child : dir->children) + func_descend(&ns_child, level + 1); + + break; + } + case node_type::value: + // Do nothing. + break; + default: + throw integrity_error("Unexpected node type!"); + } + }; + + func_descend(&m_root, 0); + + return os.str(); +} + +template +std::string rtree::export_tree_extent_as_svg() const +{ + if (traits_type::dimensions != 2u) + throw size_error("Only 2-dimensional trees are supported."); + + constexpr float min_avg_root_length = 800.0f; + constexpr float max_avg_root_length = 1000.0f; + float root_w = m_root.extent.end.d[0] - m_root.extent.start.d[0]; + float root_h = m_root.extent.end.d[1] - m_root.extent.start.d[1]; + + // Adjust zooming for optimal output size. We don't want it to be too + // large or too small. + float zoom_ratio = 1.0; + float root_avg = (root_w + root_h) / 2.0f; + if (root_avg >= max_avg_root_length) + zoom_ratio = max_avg_root_length / root_avg; + if (root_avg <= min_avg_root_length) + zoom_ratio = min_avg_root_length / root_avg; + + root_w *= zoom_ratio; + root_h *= zoom_ratio; + float root_x = m_root.extent.start.d[0] * zoom_ratio; + float root_y = m_root.extent.start.d[1] * zoom_ratio; + float r = root_avg / 100.0f * zoom_ratio; + float stroke_w = r / 10.0f; // stroke width + + const std::string indent = " "; + + // Uniform attributes to use for all drawing objects. + + std::string attrs_dir; + { + std::ostringstream os; + os << " stroke=\"#999999\" stroke-width=\"" << stroke_w << "\" fill=\"green\" fill-opacity=\"0.05\""; + attrs_dir = os.str(); + } + + std::string attrs_value; + { + std::ostringstream os; + os << " stroke=\"brown\" stroke-width=\"" << stroke_w << "\" fill=\"brown\" fill-opacity=\"0.2\""; + attrs_value = os.str(); + } + + std::ostringstream os; + os << "\n"; + os << "\n"; + + std::function func_descend = [&](const node_store* ns, int level) { + const extent_type& ext = ns->extent; + + float w = ext.end.d[0] - ext.start.d[0]; + float h = ext.end.d[1] - ext.start.d[1]; + float x = ext.start.d[0]; + float y = ext.start.d[1]; + w *= zoom_ratio; + h *= zoom_ratio; + x *= zoom_ratio; + y *= zoom_ratio; + x -= root_x; + y -= root_y; + + if (level > 0) + { + const char* attrs = (ns->type == node_type::value) ? attrs_value.data() : attrs_dir.data(); + + if (ext.is_point()) + { + os << indent << "\n"; + } + else + { + os << indent << "\n"; + } + } + + switch (ns->type) + { + case node_type::directory_leaf: + case node_type::directory_nonleaf: + { + const directory_node* dir = static_cast(ns->node_ptr); + + for (const node_store& ns_child : dir->children) + func_descend(&ns_child, level + 1); + + break; + } + case node_type::value: + // Do nothing. + break; + default: + throw integrity_error("Unexpected node type!"); + } + }; + + os << "\n"; + func_descend(&m_root, 0); + os << ""; + + return os.str(); +} + +template +void rtree::split_node(node_store* ns) +{ + directory_node* dir = ns->get_directory_node(); + + assert(dir); + assert(ns->count == traits_type::max_node_size + 1); + + dir_store_type& children = dir->children; + + sort_dir_store_by_split_dimension(children); + + size_t dist = pick_optimal_distribution(children); + distribution dist_picked(dist, children); + + // Move the child nodes in group 2 into a brand-new sibling node. + node_store node_g2 = node_store::create_leaf_directory_node(); + node_g2.type = ns->type; + directory_node* dir_sibling = static_cast(node_g2.node_ptr); + + for (auto it = dist_picked.g2.begin; it != dist_picked.g2.end; ++it) + { + assert(!it->valid_pointer); + dir_sibling->children.push_back(std::move(*it)); + } + + node_g2.count = dir_sibling->children.size(); + node_g2.pack(); + + // Remove the nodes in group 2 from the original node by shrinking the node store. + ns->count = dist_picked.g1.size; + assert(dist_picked.g1.size < dir->children.size()); + dir->children.resize(dist_picked.g1.size); + ns->pack(); // Re-calculate the bounding box. + + if (ns->is_root()) + { + // Create a new root node and make it the parent of the original root + // and the new sibling nodes. + assert(ns == &m_root); + node_store node_g1 = node_store::create_nonleaf_directory_node(); + m_root.swap(node_g1); + node_g1.parent = &m_root; + node_g2.parent = &m_root; + directory_node* dir_root = static_cast(m_root.node_ptr); + dir_root->children.emplace_back(std::move(node_g1)); + dir_root->children.emplace_back(std::move(node_g2)); + m_root.count = 2; + m_root.pack(); + + for (node_store& ns_child : dir_root->children) + ns_child.reset_parent_pointers_of_children(); + } + else + { + // Place the new sibling (node_g2) under the same parent as ns. + assert(ns->parent); + node_g2.parent = ns->parent; + node_store* ns_parent = ns->parent; + assert(ns_parent->type == node_type::directory_nonleaf); + directory_node* dir_parent = static_cast(ns_parent->node_ptr); + dir_parent->children.emplace_back(std::move(node_g2)); + ++ns_parent->count; + bool parent_size_changed = ns_parent->pack(); + + // Update the parent pointer of the children _after_ the group 2 node + // has been inserted into the buffer, as the pointer value of the node + // changes after the insertion. + ns->reset_parent_pointers(); + dir_parent->children.back().reset_parent_pointers_of_children(); + + if (ns_parent->count > traits_type::max_node_size) + // The parent node is overfilled. Split it and keep working upward. + split_node(ns_parent); + else if (parent_size_changed) + // The extent of the parent node has changed. Propagate the change upward. + ns_parent->pack_upward(); + } +} + +template +void rtree::perform_forced_reinsertion( + node_store* ns, std::unordered_set& reinserted_depth) +{ + assert(ns->count == traits_type::max_node_size + 1); + + // Compute the distance between the centers of the value extents and the + // center of the extent of the parent directory. + + point_type center_of_dir = detail::rtree::get_center_point(ns->extent); + + directory_node* dir = ns->get_directory_node(); + assert(dir); + + using buckets_type = std::vector>; + buckets_type buckets; + buckets.reserve(ns->count); + + size_t pos = 0; + for (const node_store& ns_child : dir->children) + { + buckets.emplace_back(); + buckets.back().src_pos = pos++; + + point_type center_of_child = detail::rtree::get_center_point(ns_child.extent); + buckets.back().distance = detail::rtree::calc_square_distance(center_of_dir, center_of_child); + } + + // Sort the value entries in decreasing order of their distances. + + std::sort( + buckets.begin(), buckets.end(), + [](const typename buckets_type::value_type& left, const typename buckets_type::value_type& right) -> bool { + return left.distance < right.distance; + }); + + assert(traits_type::reinsertion_size < buckets.size()); + + // Remove the first set of entries from the parent directory. + std::deque nodes_to_reinsert(traits_type::reinsertion_size); + + for (size_t i = 0; i < traits_type::reinsertion_size; ++i) + { + size_t this_pos = buckets[i].src_pos; + dir->children[this_pos].swap(nodes_to_reinsert[i]); + } + + // Erase the swapped out nodes from the directory. + auto it = std::remove_if(dir->children.begin(), dir->children.end(), [](const node_store& this_ns) -> bool { + return this_ns.type == node_type::unspecified; + }); + + dir->children.erase(it, dir->children.end()); + ns->count -= nodes_to_reinsert.size(); + assert(ns->count == dir->children.size()); + + // No need to invalidate pointers since they are all value nodes. + + if (ns->pack()) + ns->pack_upward(); + + // Re-insert the values from the closest to farthest. + + while (!nodes_to_reinsert.empty()) + { + node_store ns_to_reinsert(std::move(nodes_to_reinsert.front())); + nodes_to_reinsert.pop_front(); + + insert(std::move(ns_to_reinsert), &reinserted_depth); + } +} + +template +void rtree::sort_dir_store_by_split_dimension(dir_store_type& children) +{ + // Store the sum of margins for each dimension axis. + detail::rtree::min_value_pos min_margin_dim; + + for (size_t dim = 0; dim < traits_type::dimensions; ++dim) + { + // Sort the entries by the lower then by the upper value of their + // bounding boxes. This invalidates the pointers of the child nodes. + sort_dir_store_by_dimension(dim, children); + + key_type sum_of_margins = key_type(); // it's actually the sum of half margins. + + for (size_t dist = 1; dist <= max_dist_size; ++dist) + { + // The first group contains m-1+dist entries, while the second + // group contains the rest. + + auto it = children.begin(); + auto it_end = it; + std::advance(it_end, traits_type::min_node_size - 1 + dist); + + extent_type bb1 = detail::rtree::calc_extent(it, it_end); + it = it_end; + it_end = children.end(); + assert(it != it_end); + extent_type bb2 = detail::rtree::calc_extent(it, it_end); + + // Compute the half margins of the first and second groups. + key_type margin1 = detail::rtree::calc_half_margin(bb1); + key_type margin2 = detail::rtree::calc_half_margin(bb2); + key_type margins = margin1 + margin2; + + sum_of_margins += margins; + } + + min_margin_dim.assign(sum_of_margins, dim); + } + + // Pick the dimension axis with the lowest sum of margins. + size_t min_dim = min_margin_dim.pos; + + sort_dir_store_by_dimension(min_dim, children); +} + +template +void rtree::sort_dir_store_by_dimension(size_t dim, dir_store_type& children) +{ + std::sort(children.begin(), children.end(), [dim](const node_store& a, const node_store& b) -> bool { + if (a.extent.start.d[dim] != b.extent.start.d[dim]) + return a.extent.start.d[dim] < b.extent.start.d[dim]; + + return a.extent.end.d[dim] < b.extent.end.d[dim]; + }); + + for (node_store& ns : children) + ns.valid_pointer = false; +} + +template +size_t rtree::pick_optimal_distribution(dir_store_type& children) const +{ + // Along the chosen dimension axis, pick the distribution with the minimum + // overlap value. + detail::rtree::min_value_pos min_overlap_dist; + + for (size_t dist = 1; dist <= max_dist_size; ++dist) + { + // The first group contains m-1+dist entries, while the second + // group contains the rest. + distribution dist_data(dist, children); + extent_type bb1 = detail::rtree::calc_extent(dist_data.g1.begin, dist_data.g1.end); + extent_type bb2 = detail::rtree::calc_extent(dist_data.g2.begin, dist_data.g2.end); + + key_type overlap = detail::rtree::calc_intersection(bb1, bb2); + min_overlap_dist.assign(overlap, dist); + } + + return min_overlap_dist.pos; +} + +template +typename rtree::insertion_point rtree< + KeyT, ValueT, Traits>::find_leaf_directory_node_for_insertion(const extent_type& bb) +{ + insertion_point ret; + ret.ns = &m_root; + + for (size_t i = 0; i <= traits_type::max_tree_depth; ++i) + { + if (ret.ns->type == node_type::directory_leaf) + { + ret.depth = i; + return ret; + } + + assert(ret.ns->type == node_type::directory_nonleaf); + + directory_node* dir = static_cast(ret.ns->node_ptr); + + // If this non-leaf directory contains at least one leaf directory, + // pick the entry with minimum overlap increase. If all of its child + // nodes are non-leaf directories, then pick the entry with minimum + // area enlargement. + + if (dir->has_leaf_directory()) + ret.ns = dir->get_child_with_minimal_overlap(bb); + else + ret.ns = dir->get_child_with_minimal_area_enlargement(bb); + } + + throw std::runtime_error("Maximum tree depth has been reached."); +} + +template +typename rtree::node_store* rtree< + KeyT, ValueT, Traits>::find_nonleaf_directory_node_for_insertion(const extent_type& bb, size_t max_depth) +{ + node_store* dst = &m_root; + + for (size_t i = 0; i <= traits_type::max_tree_depth; ++i) + { + assert(dst->is_directory()); + + if (!dst->count) + // This node has no children. + return dst; + + assert(dst->type == node_type::directory_nonleaf); + + if (i == max_depth) + return dst; + + directory_node* dir = static_cast(dst->node_ptr); + + if (dir->has_leaf_directory()) + return dst; + + assert(dst->type == node_type::directory_nonleaf); + dst = dir->get_child_with_minimal_area_enlargement(bb); + assert(dst); + } + + throw std::runtime_error("Maximum tree depth has been reached."); +} + +template +template +void rtree::descend_with_func(FuncT func) const +{ + std::function func_descend = [&](const node_store* ns) { + node_properties np; + np.type = ns->type; + np.extent = ns->extent; + func(np); + + switch (ns->type) + { + case node_type::directory_leaf: + case node_type::directory_nonleaf: + { + const directory_node* dir = static_cast(ns->node_ptr); + + for (const node_store& ns_child : dir->children) + func_descend(&ns_child); + + break; + } + case node_type::value: + // Do nothing. + break; + default: + assert(!"The tree should not contain node of this type!"); + } + }; + + func_descend(&m_root); +} + +template +template +void rtree::search_descend( + size_t depth, const search_condition_type& dir_cond, const search_condition_type& value_cond, + typename _ResT::node_store_type& ns, _ResT& results) const +{ + switch (ns.type) + { + case node_type::directory_nonleaf: + case node_type::directory_leaf: + { + if (!dir_cond(ns)) + return; + + auto* dir_node = ns.get_directory_node(); + for (auto& child : dir_node->children) + search_descend(depth + 1, dir_cond, value_cond, child, results); + break; + } + case node_type::value: + { + if (!value_cond(ns)) + return; + + results.add_node_store(&ns, depth); + break; + } + case node_type::unspecified: + throw std::runtime_error("unspecified node type."); + } +} + +template +void rtree::shrink_tree_upward(node_store* ns, const extent_type& bb_affected) +{ + if (!ns) + return; + + // Check if the affected bounding box is at a corner. + if (!ns->extent.contains_at_boundary(bb_affected)) + return; + + extent_type original_bb = ns->extent; // Store the original bounding box before the packing. + bool updated = ns->pack(); + + if (!updated) + // The extent hasn't changed. There is no point going upward. + return; + + shrink_tree_upward(ns->parent, original_bb); +} + +} // namespace mdds + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/segment_tree.hpp b/include/mdds/segment_tree.hpp new file mode 100644 index 0000000..8414b72 --- /dev/null +++ b/include/mdds/segment_tree.hpp @@ -0,0 +1,704 @@ +/************************************************************************* + * + * Copyright (c) 2010-2015 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_SEGMENTTREE_HPP +#define INCLUDED_MDDS_SEGMENTTREE_HPP + +#include "mdds/node.hpp" +#include "mdds/global.hpp" + +#include +#include +#include +#include +#include + +#ifdef MDDS_UNIT_TEST +#include +#endif + +namespace mdds { + +template +class segment_tree +{ +public: + typedef _Key key_type; + typedef _Value value_type; + typedef size_t size_type; + typedef std::vector search_results_type; + +#ifdef MDDS_UNIT_TEST + struct segment_data + { + key_type begin_key; + key_type end_key; + value_type pdata; + + segment_data(key_type _beg, key_type _end, value_type p) : begin_key(_beg), end_key(_end), pdata(p) + {} + + bool operator==(const segment_data& r) const + { + return begin_key == r.begin_key && end_key == r.end_key && pdata == r.pdata; + } + + bool operator!=(const segment_data& r) const + { + return !operator==(r); + } + }; + + struct segment_map_printer + { + void operator()(const ::std::pair>& r) const + { + using namespace std; + cout << r.second.first << "-" << r.second.second << ": " << r.first->name << endl; + } + }; +#endif + +public: + typedef ::std::vector data_chain_type; + typedef std::unordered_map> segment_map_type; + typedef ::std::map> sorted_segment_map_type; + + struct nonleaf_value_type + { + key_type low; /// low range value (inclusive) + key_type high; /// high range value (non-inclusive) + data_chain_type* data_chain; + + bool operator==(const nonleaf_value_type& r) const + { + return low == r.low && high == r.high && data_chain == r.data_chain; + } + }; + + struct leaf_value_type + { + key_type key; + data_chain_type* data_chain; + + bool operator==(const leaf_value_type& r) const + { + return key == r.key && data_chain == r.data_chain; + } + }; + + struct fill_nonleaf_value_handler; + struct init_handler; + struct dispose_handler; +#ifdef MDDS_UNIT_TEST + struct to_string_handler; +#endif + + typedef __st::node node; + typedef typename node::node_ptr node_ptr; + + typedef typename __st::nonleaf_node nonleaf_node; + + struct fill_nonleaf_value_handler + { + void operator()( + __st::nonleaf_node& _self, const __st::node_base* left_node, + const __st::node_base* right_node) + { + // Parent node should carry the range of all of its child nodes. + if (left_node) + { + _self.value_nonleaf.low = left_node->is_leaf + ? static_cast(left_node)->value_leaf.key + : static_cast(left_node)->value_nonleaf.low; + } + else + { + // Having a left node is prerequisite. + throw general_error("segment_tree::fill_nonleaf_value_handler: Having a left node is prerequisite."); + } + + if (right_node) + { + if (right_node->is_leaf) + { + // When the child nodes are leaf nodes, the upper bound + // must be the value of the node that comes after the + // right leaf node (if such node exists). + + const node* p = static_cast(right_node); + if (p->next) + _self.value_nonleaf.high = p->next->value_leaf.key; + else + _self.value_nonleaf.high = p->value_leaf.key; + } + else + { + _self.value_nonleaf.high = static_cast(right_node)->value_nonleaf.high; + } + } + else + { + _self.value_nonleaf.high = left_node->is_leaf + ? static_cast(left_node)->value_leaf.key + : static_cast(left_node)->value_nonleaf.high; + } + } + }; + +#ifdef MDDS_UNIT_TEST + struct to_string_handler + { + std::string operator()(const node& _self) const + { + std::ostringstream os; + os << "[" << _self.value_leaf.key << "] "; + return os.str(); + } + + std::string operator()(const __st::nonleaf_node& _self) const + { + std::ostringstream os; + os << "[" << _self.value_nonleaf.low << "-" << _self.value_nonleaf.high << ")"; + if (_self.value_nonleaf.data_chain) + { + os << " { "; + typename data_chain_type::const_iterator itr, itr_beg = _self.value_nonleaf.data_chain->begin(), + itr_end = _self.value_nonleaf.data_chain->end(); + for (itr = itr_beg; itr != itr_end; ++itr) + { + if (itr != itr_beg) + os << ", "; + os << (*itr)->name; + } + os << " }"; + } + os << " "; + return os.str(); + } + }; +#endif + + struct init_handler + { + void operator()(node& _self) + { + _self.value_leaf.data_chain = nullptr; + } + + void operator()(__st::nonleaf_node& _self) + { + _self.value_nonleaf.data_chain = nullptr; + } + }; + + struct dispose_handler + { + void operator()(node& _self) + { + delete _self.value_leaf.data_chain; + } + + void operator()(__st::nonleaf_node& _self) + { + delete _self.value_nonleaf.data_chain; + } + }; + +#ifdef MDDS_UNIT_TEST + struct node_printer + { + void operator()(const __st::node_base* p) const + { + if (p->is_leaf) + std::cout << static_cast(p)->to_string() << " "; + else + std::cout << static_cast(p)->to_string() << " "; + } + }; +#endif + +private: + /** + * This base class takes care of collecting data chain pointers during + * tree descend for search. + */ + class search_results_base + { + public: + typedef std::vector res_chains_type; + typedef std::shared_ptr res_chains_ptr; + + public: + search_results_base() : mp_res_chains(static_cast(nullptr)) + {} + + search_results_base(const search_results_base& r) : mp_res_chains(r.mp_res_chains) + {} + + size_t size() const + { + size_t combined = 0; + if (!mp_res_chains) + return combined; + + typename res_chains_type::const_iterator itr = mp_res_chains->begin(), itr_end = mp_res_chains->end(); + for (; itr != itr_end; ++itr) + combined += (*itr)->size(); + return combined; + } + + void push_back_chain(data_chain_type* chain) + { + if (!chain || chain->empty()) + return; + + if (!mp_res_chains) + mp_res_chains.reset(new res_chains_type); + mp_res_chains->push_back(chain); + } + + res_chains_ptr& get_res_chains() + { + return mp_res_chains; + } + + private: + res_chains_ptr mp_res_chains; + }; + + class iterator_base + { + protected: + typedef typename search_results_base::res_chains_type res_chains_type; + typedef typename search_results_base::res_chains_ptr res_chains_ptr; + + iterator_base(const res_chains_ptr& p) : mp_res_chains(p), m_end_pos(true) + {} + + public: + typedef ::std::bidirectional_iterator_tag iterator_category; + typedef typename data_chain_type::value_type value_type; + typedef typename data_chain_type::pointer pointer; + typedef typename data_chain_type::reference reference; + typedef typename data_chain_type::difference_type difference_type; + + iterator_base() : mp_res_chains(static_cast(nullptr)), m_end_pos(true) + {} + + iterator_base(const iterator_base& r) + : mp_res_chains(r.mp_res_chains), m_cur_chain(r.m_cur_chain), m_cur_pos_in_chain(r.m_cur_pos_in_chain), + m_end_pos(r.m_end_pos) + {} + + iterator_base& operator=(const iterator_base& r) + { + mp_res_chains = r.mp_res_chains; + m_cur_chain = r.m_cur_chain; + m_cur_pos_in_chain = r.m_cur_pos_in_chain; + m_end_pos = r.m_end_pos; + return *this; + } + + typename data_chain_type::value_type* operator++() + { + // We don't check for end position flag for performance reasons. + // The caller is responsible for making sure not to increment past + // end position. + + // When reaching the end position, the internal iterators still + // need to be pointing at the last item before the end position. + // This is why we need to make copies of the iterators, and copy + // them back once done. + + typename data_chain_type::iterator cur_pos_in_chain = m_cur_pos_in_chain; + + if (++cur_pos_in_chain == (*m_cur_chain)->end()) + { + // End of current chain. Inspect the next chain if exists. + typename res_chains_type::iterator cur_chain = m_cur_chain; + ++cur_chain; + if (cur_chain == mp_res_chains->end()) + { + m_end_pos = true; + return nullptr; + } + m_cur_chain = cur_chain; + m_cur_pos_in_chain = (*m_cur_chain)->begin(); + } + else + ++m_cur_pos_in_chain; + + return operator->(); + } + + typename data_chain_type::value_type* operator--() + { + if (!mp_res_chains) + return nullptr; + + if (m_end_pos) + { + m_end_pos = false; + return &(*m_cur_pos_in_chain); + } + + if (m_cur_pos_in_chain == (*m_cur_chain)->begin()) + { + if (m_cur_chain == mp_res_chains->begin()) + { + // Already at the first data chain. Don't move the iterator position. + return nullptr; + } + --m_cur_chain; + m_cur_pos_in_chain = (*m_cur_chain)->end(); + } + --m_cur_pos_in_chain; + return operator->(); + } + + bool operator==(const iterator_base& r) const + { + if (mp_res_chains.get()) + { + // non-empty result set. + return mp_res_chains.get() == r.mp_res_chains.get() && m_cur_chain == r.m_cur_chain && + m_cur_pos_in_chain == r.m_cur_pos_in_chain && m_end_pos == r.m_end_pos; + } + + // empty result set. + if (r.mp_res_chains.get()) + return false; + return m_end_pos == r.m_end_pos; + } + + bool operator!=(const iterator_base& r) const + { + return !operator==(r); + } + + typename data_chain_type::value_type& operator*() + { + return *m_cur_pos_in_chain; + } + + typename data_chain_type::value_type* operator->() + { + return &(*m_cur_pos_in_chain); + } + + protected: + void move_to_front() + { + if (!mp_res_chains) + { + // Empty data set. + m_end_pos = true; + return; + } + + // We assume that there is at least one chain list, and no + // empty chain list exists. So, skip the check. + m_cur_chain = mp_res_chains->begin(); + m_cur_pos_in_chain = (*m_cur_chain)->begin(); + m_end_pos = false; + } + + void move_to_end() + { + m_end_pos = true; + if (!mp_res_chains) + // Empty data set. + return; + + m_cur_chain = mp_res_chains->end(); + --m_cur_chain; + m_cur_pos_in_chain = (*m_cur_chain)->end(); + --m_cur_pos_in_chain; + } + + private: + res_chains_ptr mp_res_chains; + typename res_chains_type::iterator m_cur_chain; + typename data_chain_type::iterator m_cur_pos_in_chain; + bool m_end_pos : 1; + }; + +public: + class search_results : public search_results_base + { + typedef typename search_results_base::res_chains_type res_chains_type; + typedef typename search_results_base::res_chains_ptr res_chains_ptr; + + public: + class iterator : public iterator_base + { + friend class segment_tree<_Key, _Value>::search_results; + + private: + iterator(const res_chains_ptr& p) : iterator_base(p) + {} + + public: + iterator() : iterator_base() + {} + }; + + typename search_results::iterator begin() + { + typename search_results::iterator itr(search_results_base::get_res_chains()); + itr.move_to_front(); + return itr; + } + + typename search_results::iterator end() + { + typename search_results::iterator itr(search_results_base::get_res_chains()); + itr.move_to_end(); + return itr; + } + }; + + class search_result_vector_inserter + { + public: + search_result_vector_inserter(search_results_type& result) : m_result(result) + {} + void operator()(data_chain_type* node_data) + { + if (!node_data) + return; + + typename data_chain_type::const_iterator itr = node_data->begin(), itr_end = node_data->end(); + for (; itr != itr_end; ++itr) + m_result.push_back(*itr); + } + + private: + search_results_type& m_result; + }; + + class search_result_inserter + { + public: + search_result_inserter(search_results_base& result) : m_result(result) + {} + void operator()(data_chain_type* node_data) + { + if (!node_data) + return; + + m_result.push_back_chain(node_data); + } + + private: + search_results_base& m_result; + }; + + segment_tree(); + segment_tree(const segment_tree& r); + ~segment_tree(); + + /** + * Equality between two segment_tree instances is evaluated by comparing + * the segments that they store. The trees are not compared. + */ + bool operator==(const segment_tree& r) const; + + bool operator!=(const segment_tree& r) const + { + return !operator==(r); + } + + /** + * Check whether or not the internal tree is in a valid state. The tree + * must be valid in order to perform searches. + * + * @return true if the tree is valid, false otherwise. + */ + bool is_tree_valid() const + { + return m_valid_tree; + } + + /** + * Build or re-build tree based on the current set of segments. + */ + void build_tree(); + + /** + * Insert a new segment. + * + * @param begin_key begin point of the segment. The value is inclusive. + * @param end_key end point of the segment. The value is non-inclusive. + * @param pdata pointer to the data instance associated with this segment. + * Note that the caller must manage the life cycle of the + * data instance. + */ + bool insert(key_type begin_key, key_type end_key, value_type pdata); + + /** + * Search the tree and collect all segments that include a specified + * point. + * + * @param point specified point value + * @param result doubly-linked list of data instances associated with + * the segments that include the specified point. + * Note that the search result gets appended to the + * list; the list will not get emptied on each + * search. It is caller's responsibility to empty + * the list before passing it to this method in case the + * caller so desires. + * + * @return true if the search is performed successfully, false if the + * search has ended prematurely due to error conditions. + */ + bool search(key_type point, search_results_type& result) const; + + /** + * Search the tree and collect all segments that include a specified + * point. + * + * @param point specified point value + * + * @return object containing the result of the search, which can be + * accessed via iterator. + */ + search_results search(key_type point) const; + + /** + * Remove a segment that matches by the value. This will not + * invalidate the tree; however, if you have removed lots of segments, you + * might want to re-build the tree to shrink its size. + * + * @param value value to remove a segment by. + */ + void remove(value_type value); + + /** + * Remove all segments data. + */ + void clear(); + + /** + * Return the number of segments currently stored in this container. + */ + size_t size() const; + + /** + * Return whether or not the container stores any segments or none at all. + */ + bool empty() const; + + /** + * Return the number of leaf nodes. + * + * @return number of leaf nodes. + */ + size_t leaf_size() const; + +#ifdef MDDS_UNIT_TEST + void dump_tree() const; + void dump_leaf_nodes() const; + void dump_segment_data() const; + bool verify_node_lists() const; + + struct leaf_node_check + { + key_type key; + data_chain_type data_chain; + }; + + bool verify_leaf_nodes(const ::std::vector& checks) const; + + /** + * Verify the validity of the segment data array. + * + * @param checks null-terminated array of expected values. The last item + * must have a nullptr pdata value to terminate the array. + */ + bool verify_segment_data(const segment_map_type& checks) const; +#endif + +private: + /** + * To be called from rectangle_set. + */ + void search(key_type point, search_results_base& result) const; + + typedef std::vector<__st::node_base*> node_list_type; + typedef std::map> data_node_map_type; + + static void create_leaf_node_instances(const ::std::vector& keys, node_ptr& left, node_ptr& right); + + /** + * Descend the tree from the root node, and mark appropriate nodes, both + * leaf and non-leaf, based on segment's end points. When marking nodes, + * record their positions as a list of node pointers. + */ + void descend_tree_and_mark( + __st::node_base* pnode, value_type pdata, key_type begin_key, key_type end_key, node_list_type* plist); + + void build_leaf_nodes(); + + /** + * Go through the list of nodes, and remove the specified data pointer + * value from the nodes. + */ + void remove_data_from_nodes(node_list_type* plist, const value_type pdata); + void remove_data_from_chain(data_chain_type& chain, const value_type pdata); + + void clear_all_nodes(); + +#ifdef MDDS_UNIT_TEST + static bool has_data_pointer(const node_list_type& node_list, const value_type pdata); + static void print_leaf_value(const leaf_value_type& v); +#endif + +private: + std::vector m_nonleaf_node_pool; + + segment_map_type m_segment_data; + + /** + * For each data pointer, it keeps track of all nodes, leaf or non-leaf, + * that stores the data pointer label. This data is used when removing + * segments by the data pointer value. + */ + data_node_map_type m_tagged_node_map; + + nonleaf_node* m_root_node; + node_ptr m_left_leaf; + node_ptr m_right_leaf; + bool m_valid_tree : 1; +}; + +} // namespace mdds + +#include "segment_tree_def.inl" + +#endif diff --git a/include/mdds/segment_tree_def.inl b/include/mdds/segment_tree_def.inl new file mode 100644 index 0000000..011449c --- /dev/null +++ b/include/mdds/segment_tree_def.inl @@ -0,0 +1,644 @@ +/************************************************************************* + * + * Copyright (c) 2015 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#include + +namespace mdds { + +namespace __st { + +template +void descend_tree_for_search(typename T::key_type point, const __st::node_base* pnode, _Inserter& result) +{ + typedef typename T::node leaf_node; + typedef typename T::nonleaf_node nonleaf_node; + + typedef typename T::nonleaf_value_type nonleaf_value_type; + typedef typename T::leaf_value_type leaf_value_type; + + if (!pnode) + // This should never happen, but just in case. + return; + + if (pnode->is_leaf) + { + result(static_cast(pnode)->value_leaf.data_chain); + return; + } + + const nonleaf_node* pnonleaf = static_cast(pnode); + const nonleaf_value_type& v = pnonleaf->value_nonleaf; + if (point < v.low || v.high <= point) + // Query point is out-of-range. + return; + + result(v.data_chain); + + // Check the left child node first, then the right one. + __st::node_base* pchild = pnonleaf->left; + if (!pchild) + return; + + assert(pnonleaf->right ? pchild->is_leaf == pnonleaf->right->is_leaf : true); + + if (pchild->is_leaf) + { + // The child node are leaf nodes. + const leaf_value_type& vleft = static_cast(pchild)->value_leaf; + if (point < vleft.key) + { + // Out-of-range. Nothing more to do. + return; + } + + if (pnonleaf->right) + { + assert(pnonleaf->right->is_leaf); + const leaf_value_type& vright = static_cast(pnonleaf->right)->value_leaf; + if (vright.key <= point) + // Follow the right node. + pchild = pnonleaf->right; + } + } + else + { + // This child nodes are non-leaf nodes. + + const nonleaf_value_type& vleft = static_cast(pchild)->value_nonleaf; + + if (point < vleft.low) + { + // Out-of-range. Nothing more to do. + return; + } + if (vleft.high <= point && pnonleaf->right) + // Follow the right child. + pchild = pnonleaf->right; + + assert( + static_cast(pchild)->value_nonleaf.low <= point && + point < static_cast(pchild)->value_nonleaf.high); + } + + descend_tree_for_search(point, pchild, result); +} + +} // namespace __st + +template +segment_tree<_Key, _Value>::segment_tree() : m_root_node(nullptr), m_valid_tree(false) +{} + +template +segment_tree<_Key, _Value>::segment_tree(const segment_tree& r) + : m_segment_data(r.m_segment_data), m_root_node(nullptr), m_valid_tree(r.m_valid_tree) +{ + if (m_valid_tree) + build_tree(); +} + +template +segment_tree<_Key, _Value>::~segment_tree() +{ + clear_all_nodes(); +} + +template +bool segment_tree<_Key, _Value>::operator==(const segment_tree& r) const +{ + if (m_valid_tree != r.m_valid_tree) + return false; + + // Sort the data by key values first. + sorted_segment_map_type seg1(m_segment_data.begin(), m_segment_data.end()); + sorted_segment_map_type seg2(r.m_segment_data.begin(), r.m_segment_data.end()); + typename sorted_segment_map_type::const_iterator itr1 = seg1.begin(), itr1_end = seg1.end(); + typename sorted_segment_map_type::const_iterator itr2 = seg2.begin(), itr2_end = seg2.end(); + + for (; itr1 != itr1_end; ++itr1, ++itr2) + { + if (itr2 == itr2_end) + return false; + + if (*itr1 != *itr2) + return false; + } + if (itr2 != itr2_end) + return false; + + return true; +} + +template +void segment_tree<_Key, _Value>::build_tree() +{ + build_leaf_nodes(); + m_nonleaf_node_pool.clear(); + + // Count the number of leaf nodes. + size_t leaf_count = __st::count_leaf_nodes(m_left_leaf.get(), m_right_leaf.get()); + + // Determine the total number of non-leaf nodes needed to build the whole tree. + size_t nonleaf_count = __st::count_needed_nonleaf_nodes(leaf_count); + + m_nonleaf_node_pool.resize(nonleaf_count); + + mdds::__st::tree_builder builder(m_nonleaf_node_pool); + m_root_node = builder.build(m_left_leaf); + + // Start "inserting" all segments from the root. + typename segment_map_type::const_iterator itr, itr_beg = m_segment_data.begin(), itr_end = m_segment_data.end(); + + data_node_map_type tagged_node_map; + for (itr = itr_beg; itr != itr_end; ++itr) + { + value_type pdata = itr->first; + auto r = + tagged_node_map.insert(typename data_node_map_type::value_type(pdata, std::make_unique())); + + node_list_type* plist = r.first->second.get(); + plist->reserve(10); + + descend_tree_and_mark(m_root_node, pdata, itr->second.first, itr->second.second, plist); + } + + m_tagged_node_map.swap(tagged_node_map); + m_valid_tree = true; +} + +template +void segment_tree<_Key, _Value>::descend_tree_and_mark( + __st::node_base* pnode, value_type pdata, key_type begin_key, key_type end_key, node_list_type* plist) +{ + if (!pnode) + return; + + if (pnode->is_leaf) + { + // This is a leaf node. + node* pleaf = static_cast(pnode); + if (begin_key <= pleaf->value_leaf.key && pleaf->value_leaf.key < end_key) + { + leaf_value_type& v = pleaf->value_leaf; + if (!v.data_chain) + v.data_chain = new data_chain_type; + v.data_chain->push_back(pdata); + plist->push_back(pnode); + } + return; + } + + nonleaf_node* pnonleaf = static_cast(pnode); + if (end_key < pnonleaf->value_nonleaf.low || pnonleaf->value_nonleaf.high <= begin_key) + return; + + nonleaf_value_type& v = pnonleaf->value_nonleaf; + if (begin_key <= v.low && v.high < end_key) + { + // mark this non-leaf node and stop. + if (!v.data_chain) + v.data_chain = new data_chain_type; + v.data_chain->push_back(pdata); + plist->push_back(pnode); + return; + } + + descend_tree_and_mark(pnonleaf->left, pdata, begin_key, end_key, plist); + descend_tree_and_mark(pnonleaf->right, pdata, begin_key, end_key, plist); +} + +template +void segment_tree<_Key, _Value>::build_leaf_nodes() +{ + using namespace std; + + disconnect_leaf_nodes(m_left_leaf.get(), m_right_leaf.get()); + + // In 1st pass, collect unique end-point values and sort them. + vector keys_uniq; + keys_uniq.reserve(m_segment_data.size() * 2); + typename segment_map_type::const_iterator itr, itr_beg = m_segment_data.begin(), itr_end = m_segment_data.end(); + for (itr = itr_beg; itr != itr_end; ++itr) + { + keys_uniq.push_back(itr->second.first); + keys_uniq.push_back(itr->second.second); + } + + // sort and remove duplicates. + sort(keys_uniq.begin(), keys_uniq.end()); + keys_uniq.erase(unique(keys_uniq.begin(), keys_uniq.end()), keys_uniq.end()); + + create_leaf_node_instances(keys_uniq, m_left_leaf, m_right_leaf); +} + +template +void segment_tree<_Key, _Value>::create_leaf_node_instances( + const ::std::vector& keys, node_ptr& left, node_ptr& right) +{ + if (keys.empty() || keys.size() < 2) + // We need at least two keys in order to build tree. + return; + + typename ::std::vector::const_iterator itr = keys.begin(), itr_end = keys.end(); + + // left-most node + left.reset(new node); + left->value_leaf.key = *itr; + + // move on to next. + left->next.reset(new node); + node_ptr prev_node = left; + node_ptr cur_node = left->next; + cur_node->prev = prev_node; + + for (++itr; itr != itr_end; ++itr) + { + cur_node->value_leaf.key = *itr; + + // move on to next + cur_node->next.reset(new node); + prev_node = cur_node; + cur_node = cur_node->next; + cur_node->prev = prev_node; + } + + // Remove the excess node. + prev_node->next.reset(); + right = prev_node; +} + +template +bool segment_tree<_Key, _Value>::insert(key_type begin_key, key_type end_key, value_type pdata) +{ + if (begin_key >= end_key) + return false; + + if (m_segment_data.find(pdata) != m_segment_data.end()) + // Insertion of duplicate data is not allowed. + return false; + + ::std::pair range; + range.first = begin_key; + range.second = end_key; + m_segment_data.insert(typename segment_map_type::value_type(pdata, range)); + + m_valid_tree = false; + return true; +} + +template +bool segment_tree<_Key, _Value>::search(key_type point, search_results_type& result) const +{ + if (!m_valid_tree) + // Tree is invalidated. + return false; + + if (!m_root_node) + // Tree doesn't exist. Since the tree is flagged valid, this means no + // segments have been inserted. + return true; + + search_result_vector_inserter result_inserter(result); + typedef segment_tree<_Key, _Value> tree_type; + __st::descend_tree_for_search(point, m_root_node, result_inserter); + return true; +} + +template +typename segment_tree<_Key, _Value>::search_results segment_tree<_Key, _Value>::search(key_type point) const +{ + search_results result; + if (!m_valid_tree || !m_root_node) + return result; + + search_result_inserter result_inserter(result); + typedef segment_tree<_Key, _Value> tree_type; + __st::descend_tree_for_search(point, m_root_node, result_inserter); + + return result; +} + +template +void segment_tree<_Key, _Value>::search(key_type point, search_results_base& result) const +{ + if (!m_valid_tree || !m_root_node) + return; + + search_result_inserter result_inserter(result); + typedef segment_tree<_Key, _Value> tree_type; + __st::descend_tree_for_search(point, m_root_node, result_inserter); +} + +template +void segment_tree<_Key, _Value>::remove(value_type value) +{ + using namespace std; + + typename data_node_map_type::iterator itr = m_tagged_node_map.find(value); + if (itr != m_tagged_node_map.end()) + { + // Tagged node list found. Remove all the tags from the tree nodes. + node_list_type* plist = itr->second.get(); + if (!plist) + return; + + remove_data_from_nodes(plist, value); + + // Remove the tags associated with this pointer from the data set. + m_tagged_node_map.erase(itr); + } + + // Remove from the segment data array. + m_segment_data.erase(value); +} + +template +void segment_tree<_Key, _Value>::clear() +{ + m_tagged_node_map.clear(); + m_segment_data.clear(); + clear_all_nodes(); + m_valid_tree = false; +} + +template +size_t segment_tree<_Key, _Value>::size() const +{ + return m_segment_data.size(); +} + +template +bool segment_tree<_Key, _Value>::empty() const +{ + return m_segment_data.empty(); +} + +template +size_t segment_tree<_Key, _Value>::leaf_size() const +{ + return __st::count_leaf_nodes(m_left_leaf.get(), m_right_leaf.get()); +} + +template +void segment_tree<_Key, _Value>::remove_data_from_nodes(node_list_type* plist, const value_type pdata) +{ + typename node_list_type::iterator itr = plist->begin(), itr_end = plist->end(); + for (; itr != itr_end; ++itr) + { + data_chain_type* chain = nullptr; + __st::node_base* p = *itr; + if (p->is_leaf) + chain = static_cast(p)->value_leaf.data_chain; + else + chain = static_cast(p)->value_nonleaf.data_chain; + + if (!chain) + continue; + + remove_data_from_chain(*chain, pdata); + } +} + +template +void segment_tree<_Key, _Value>::remove_data_from_chain(data_chain_type& chain, const value_type pdata) +{ + typename data_chain_type::iterator itr = ::std::find(chain.begin(), chain.end(), pdata); + if (itr != chain.end()) + { + *itr = chain.back(); + chain.pop_back(); + } +} + +template +void segment_tree<_Key, _Value>::clear_all_nodes() +{ + disconnect_leaf_nodes(m_left_leaf.get(), m_right_leaf.get()); + m_nonleaf_node_pool.clear(); + m_left_leaf.reset(); + m_right_leaf.reset(); + m_root_node = nullptr; +} + +#ifdef MDDS_UNIT_TEST +template +void segment_tree<_Key, _Value>::dump_tree() const +{ + using ::std::cout; + using ::std::endl; + + if (!m_valid_tree) + assert(!"attempted to dump an invalid tree!"); + + cout << "dump tree ------------------------------------------------------" << endl; + size_t node_count = mdds::__st::tree_dumper::dump(m_root_node); + size_t node_instance_count = node::get_instance_count(); + + cout << "tree node count = " << node_count << " node instance count = " << node_instance_count << endl; +} + +template +void segment_tree<_Key, _Value>::dump_leaf_nodes() const +{ + using ::std::cout; + using ::std::endl; + + cout << "dump leaf nodes ------------------------------------------------" << endl; + + node* p = m_left_leaf.get(); + while (p) + { + print_leaf_value(p->value_leaf); + p = p->next.get(); + } + cout << " node instance count = " << node::get_instance_count() << endl; +} + +template +void segment_tree<_Key, _Value>::dump_segment_data() const +{ + using namespace std; + cout << "dump segment data ----------------------------------------------" << endl; + + segment_map_printer func; + for_each(m_segment_data.begin(), m_segment_data.end(), func); +} + +template +bool segment_tree<_Key, _Value>::verify_node_lists() const +{ + using namespace std; + + typename data_node_map_type::const_iterator itr = m_tagged_node_map.begin(), itr_end = m_tagged_node_map.end(); + for (; itr != itr_end; ++itr) + { + // Print stored nodes. + cout << "node list " << itr->first->name << ": "; + const node_list_type* plist = itr->second.get(); + assert(plist); + node_printer func; + for_each(plist->begin(), plist->end(), func); + cout << endl; + + // Verify that all of these nodes have the data pointer. + if (!has_data_pointer(*plist, itr->first)) + return false; + } + return true; +} + +template +bool segment_tree<_Key, _Value>::verify_leaf_nodes(const ::std::vector& checks) const +{ + using namespace std; + + node* cur_node = m_left_leaf.get(); + typename ::std::vector::const_iterator itr = checks.begin(), itr_end = checks.end(); + for (; itr != itr_end; ++itr) + { + if (!cur_node) + // Position past the right-mode node. Invalid. + return false; + + if (cur_node->value_leaf.key != itr->key) + // Key values differ. + return false; + + if (itr->data_chain.empty()) + { + if (cur_node->value_leaf.data_chain) + // The data chain should be empty (i.e. the pointer should be nullptr). + return false; + } + else + { + if (!cur_node->value_leaf.data_chain) + // This node should have data pointers! + return false; + + data_chain_type chain1 = itr->data_chain; + data_chain_type chain2 = *cur_node->value_leaf.data_chain; + + if (chain1.size() != chain2.size()) + return false; + + ::std::vector test1, test2; + test1.reserve(chain1.size()); + test2.reserve(chain2.size()); + copy(chain1.begin(), chain1.end(), back_inserter(test1)); + copy(chain2.begin(), chain2.end(), back_inserter(test2)); + + // Sort both arrays before comparing them. + sort(test1.begin(), test1.end()); + sort(test2.begin(), test2.end()); + + if (test1 != test2) + return false; + } + + cur_node = cur_node->next.get(); + } + + if (cur_node) + // At this point, we expect the current node to be at the position + // past the right-most node, which is nullptr. + return false; + + return true; +} + +template +bool segment_tree<_Key, _Value>::verify_segment_data(const segment_map_type& checks) const +{ + // Sort the data by key values first. + sorted_segment_map_type seg1(checks.begin(), checks.end()); + sorted_segment_map_type seg2(m_segment_data.begin(), m_segment_data.end()); + + typename sorted_segment_map_type::const_iterator itr1 = seg1.begin(), itr1_end = seg1.end(); + typename sorted_segment_map_type::const_iterator itr2 = seg2.begin(), itr2_end = seg2.end(); + for (; itr1 != itr1_end; ++itr1, ++itr2) + { + if (itr2 == itr2_end) + return false; + + if (*itr1 != *itr2) + return false; + } + if (itr2 != itr2_end) + return false; + + return true; +} + +template +bool segment_tree<_Key, _Value>::has_data_pointer(const node_list_type& node_list, const value_type pdata) +{ + using namespace std; + + typename node_list_type::const_iterator itr = node_list.begin(), itr_end = node_list.end(); + + for (; itr != itr_end; ++itr) + { + // Check each node, and make sure each node has the pdata pointer + // listed. + const __st::node_base* pnode = *itr; + const data_chain_type* chain = nullptr; + if (pnode->is_leaf) + chain = static_cast(pnode)->value_leaf.data_chain; + else + chain = static_cast(pnode)->value_nonleaf.data_chain; + + if (!chain) + return false; + + if (find(chain->begin(), chain->end(), pdata) == chain->end()) + return false; + } + return true; +} + +template +void segment_tree<_Key, _Value>::print_leaf_value(const leaf_value_type& v) +{ + using namespace std; + cout << v.key << ": { "; + if (v.data_chain) + { + const data_chain_type* pchain = v.data_chain; + typename data_chain_type::const_iterator itr, itr_beg = pchain->begin(), itr_end = pchain->end(); + for (itr = itr_beg; itr != itr_end; ++itr) + { + if (itr != itr_beg) + cout << ", "; + cout << (*itr)->name; + } + } + cout << " }" << endl; +} +#endif + +} // namespace mdds diff --git a/include/mdds/sorted_string_map.hpp b/include/mdds/sorted_string_map.hpp new file mode 100644 index 0000000..c7c0685 --- /dev/null +++ b/include/mdds/sorted_string_map.hpp @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2022 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#pragma once + +#include +#include + +namespace mdds { + +/** + * Single key-value entry. Caller must provide at compile time a static array + * of these entries. + * + * @param key memory address of the first character of the char buffer that + * stores the key. + * @param key_length length of the char buffer. + * @param value value associated with the key. + */ +template +struct chars_map_entry +{ + const char* key; + SizeT key_length; + ValueT value; +}; + +template +struct string_view_map_entry +{ + std::string_view key; + ValueT value; +}; + +/** + * sorted_string_map provides an efficient way to map string keys to + * arbitrary values, provided that the keys are known at compile time and + * are sorted in ascending order. + */ +template class EntryT = chars_map_entry> +class sorted_string_map +{ +public: + using value_type = ValueT; + using size_type = std::size_t; + using entry = EntryT; + + /** + * Constructor. + * + * @param entries pointer to the array of key-value entries. + * @param entry_size size of the key-value entry array. + * @param null_value null value to return when the find method fails to + * find a matching entry. + */ + sorted_string_map(const entry* entries, size_type entry_size, value_type null_value); + + /** + * Find a value associated with a specified string key. + * + * @param input pointer to a C-style string whose value represents the key + * to match. + * @param len length of the matching string value. + * + * @return value associated with the key, or the null value in case the + * key is not found. + */ + value_type find(const char* input, size_type len) const; + + /** + * Find a value associated with a specified string key. + * + * @param input string key to match. + * + * @return value associated with the key, or the null value in case the + * key is not found. + */ + value_type find(std::string_view input) const; + + /** + * Return the number of entries in the map. Since the number of entries + * is statically defined at compile time, this method always returns the + * same value. + * + * @return the number of entries in the map. + */ + size_type size() const; + +private: + const entry* m_entries; + value_type m_null_value; + size_type m_entry_size; + const entry* m_entry_end; +}; + +} // namespace mdds + +#include "sorted_string_map_def.inl" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/sorted_string_map_def.inl b/include/mdds/sorted_string_map_def.inl new file mode 100644 index 0000000..be0c081 --- /dev/null +++ b/include/mdds/sorted_string_map_def.inl @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2022 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#include "./global.hpp" + +#include +#include + +namespace mdds { + +namespace detail { + +template class EntryT> +struct entry_funcs; + +template +struct entry_funcs +{ + using entry = chars_map_entry; + using size_type = SizeT; + + static bool compare(const entry& entry1, const entry& entry2) + { + if (entry1.key_length != entry2.key_length) + { + std::size_t key_length = std::min(entry1.key_length, entry2.key_length); + int ret = std::memcmp(entry1.key, entry2.key, key_length); + if (ret == 0) + return entry1.key_length < entry2.key_length; + + return ret < 0; + } + else + { + return std::memcmp(entry1.key, entry2.key, entry1.key_length) < 0; + } + } + + static entry to_entry(const char* input, std::size_t len) + { + return entry{input, len, ValueT{}}; + } + + static const char* get_key_ptr(const entry& e) + { + return e.key; + } + + static size_type get_key_size(const entry& e) + { + return e.key_length; + } +}; + +template +struct entry_funcs +{ + using entry = string_view_map_entry; + using size_type = SizeT; + + static bool compare(const entry& entry1, const entry& entry2) + { + if (entry1.key.size() != entry2.key.size()) + { + std::size_t key_length = std::min(entry1.key.size(), entry2.key.size()); + int ret = std::memcmp(entry1.key.data(), entry2.key.data(), key_length); + if (ret == 0) + return entry1.key.size() < entry2.key.size(); + + return ret < 0; + } + else + { + return std::memcmp(entry1.key.data(), entry2.key.data(), entry1.key.size()) < 0; + } + } + + static entry to_entry(const char* input, std::size_t len) + { + return entry{{input, len}, ValueT{}}; + } + + static const char* get_key_ptr(const entry& e) + { + return e.key.data(); + } + + static size_type get_key_size(const entry& e) + { + return e.key.size(); + } +}; + +} // namespace detail + +template class EntryT> +sorted_string_map::sorted_string_map(const entry* entries, size_type entry_size, value_type null_value) + : m_entries(entries), m_null_value(null_value), m_entry_size(entry_size), m_entry_end(m_entries + m_entry_size) +{ +#ifdef MDDS_SORTED_STRING_MAP_DEBUG + using entry_funcs = detail::entry_funcs; + + if (!std::is_sorted(m_entries, m_entry_end, entry_funcs::compare)) + throw invalid_arg_error("mapped entries are not sorted"); +#endif +} + +template class EntryT> +typename sorted_string_map::value_type sorted_string_map::find( + const char* input, size_type len) const +{ + if (m_entry_size == 0) + return m_null_value; + + using entry_funcs = detail::entry_funcs; + entry ent = entry_funcs::to_entry(input, len); + + const entry* val = std::lower_bound(m_entries, m_entry_end, ent, entry_funcs::compare); + + if (val == m_entry_end || entry_funcs::get_key_size(*val) != len || + std::memcmp(entry_funcs::get_key_ptr(*val), input, len)) + return m_null_value; + + return val->value; +} + +template class EntryT> +typename sorted_string_map::value_type sorted_string_map::find( + std::string_view input) const +{ + return find(input.data(), input.size()); +} + +template class EntryT> +typename sorted_string_map::size_type sorted_string_map::size() const +{ + return m_entry_size; +} + +} // namespace mdds + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/trie_map.hpp b/include/mdds/trie_map.hpp new file mode 100644 index 0000000..c0fe6a0 --- /dev/null +++ b/include/mdds/trie_map.hpp @@ -0,0 +1,713 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2015-2020 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_TRIE_MAP_HPP +#define INCLUDED_MDDS_TRIE_MAP_HPP + +#include "trie_map_itr.hpp" + +#include +#include +#include +#include +#include + +namespace mdds { + +namespace trie { + +/** + * Template for a key type implemented using a typical STL container type. + */ +template +struct std_container_traits +{ + /** type used to store a key value. */ + using key_type = ContainerT; + + /** + * type used to build an intermediate key value, from which a final key + * value is to be created. It is expected to be an array structure whose + * content is contiguous in memory. Its elements must be of + * key_unit_type. + */ + using key_buffer_type = key_type; + + /** + * type that represents a single character inside a key or a key buffer + * object. A key object is expected to store a series of elements of this + * type. + */ + using key_unit_type = typename key_type::value_type; + + /** + * Function called to create and initialize a buffer object from a given + * initial key value. + * + * @param str pointer to the first character of the key value. + * @param length length of the key value. + * + * @return buffer object containing the specified key value. + */ + static key_buffer_type to_key_buffer(const key_unit_type* str, size_t length) + { + return key_buffer_type(str, length); + } + + /** + * Function called to create and initialize a buffer object from a given + * initial key value. + * + * @param key key value + * + * @return buffer object containing the specified key value. + */ + static key_buffer_type to_key_buffer(const key_type& key) + { + return key_buffer_type(key); + } + + static const key_unit_type* buffer_data(const key_buffer_type& buf) + { + return buf.data(); + } + + static size_t buffer_size(const key_buffer_type& buf) + { + return buf.size(); + } + + /** + * Function called to append a single character to the end of a key + * buffer. + * + * @param buffer buffer object to append character to. + * @param c character to append to the buffer. + */ + static void push_back(key_buffer_type& buffer, key_unit_type c) + { + buffer.push_back(c); + } + + /** + * Function called to remove a single character from the tail of an + * existing key buffer. + * + * @param buffer buffer object to remove character from. + */ + static void pop_back(key_buffer_type& buffer) + { + buffer.pop_back(); + } + + /** + * Function called to create a final string object from an existing + * buffer. + * + * @param buf buffer object to create a final string object from. + * + * @return string object whose content is created from the buffer object. + */ + static key_type to_key(const key_buffer_type& buf) + { + return buf; + } +}; + +using std_string_traits = std_container_traits; + +/** Serializer for numeric data types. */ +template +struct numeric_value_serializer +{ + static constexpr bool variable_size = false; + + static constexpr size_t value_size = sizeof(T); + + static void write(std::ostream& os, const T& v); + + static void read(std::istream& is, size_t n, T& v); +}; + +/** Serializer for variable-size data types. */ +template +struct variable_value_serializer +{ + static constexpr bool variable_size = true; + + static void write(std::ostream& os, const T& v); + + static void read(std::istream& is, size_t n, T& v); +}; + +/** + * Serializer for standard sequence container whose value type is of + * numeric value type. + */ +template +struct numeric_sequence_value_serializer +{ + using element_serializer = numeric_value_serializer; + + static constexpr bool variable_size = true; + + static void write(std::ostream& os, const T& v); + + static void read(std::istream& is, size_t n, T& v); +}; + +/** + * Default value serializer for mdds::packed_trie_map's load_state and + * save_state methods. The primary template is used for numeric value + * types, and template specializations exist for std::string, as + * well as sequence containers, such as std::vector, whose elements are of + * numeric types. + */ +template +struct value_serializer : numeric_value_serializer +{ +}; + +template +struct value_serializer::value>::type> + : numeric_sequence_value_serializer +{ +}; + +template<> +struct value_serializer : variable_value_serializer +{ +}; + +} // namespace trie + +template +class packed_trie_map; + +/** + * Trie map provides storage for multiple key-value pairs where keys are + * either strings, or otherwise consist of arrays of characters. The keys + * are stored in an ordered tree structure known as trie, or sometimes + * referred to as prefix tree. + */ +template +class trie_map +{ + friend class packed_trie_map; + friend class trie::detail::iterator_base; + friend class trie::detail::iterator_base; + friend class trie::detail::const_iterator; + friend class trie::detail::iterator; + friend class trie::detail::search_results; + friend trie::detail::get_node_stack_type; + friend trie::detail::get_node_stack_type; + +public: + typedef packed_trie_map packed_type; + typedef KeyTraits key_traits_type; + typedef typename key_traits_type::key_type key_type; + typedef typename key_traits_type::key_buffer_type key_buffer_type; + typedef typename key_traits_type::key_unit_type key_unit_type; + typedef ValueT value_type; + typedef size_t size_type; + typedef std::pair key_value_type; + + using const_iterator = trie::detail::const_iterator; + using iterator = trie::detail::iterator; + typedef trie::detail::search_results search_results; + +private: + struct trie_node + { + typedef std::map children_type; + + children_type children; + value_type value; + bool has_value; + + trie_node(); + trie_node(const trie_node& other); + trie_node(trie_node&& other); + + void swap(trie_node& other); + }; + + template + struct stack_item + { + using _is_const = std::bool_constant; + + using child_pos_type = + typename mdds::detail::get_iterator_type::type; + + using trie_node_type = typename mdds::detail::const_or_not::type; + + trie_node_type* node; + child_pos_type child_pos; + + stack_item(trie_node_type* _node, const child_pos_type& _child_pos) : node(_node), child_pos(_child_pos) + {} + + bool operator==(const stack_item& r) const + { + return node == r.node && child_pos == r.child_pos; + } + + bool operator!=(const stack_item& r) const + { + return !operator==(r); + } + }; + + using const_node_stack_type = std::vector>; + using node_stack_type = std::vector>; + +public: + /** + * Default constructor. + */ + trie_map(); + + trie_map(const trie_map& other); + + trie_map(trie_map&& other); + + const_iterator begin() const; + + iterator begin(); + + const_iterator end() const; + + iterator end(); + + trie_map& operator=(trie_map other); + + void swap(trie_map& other); + + /** + * Insert a new key-value pair. + * + * @param key key value. + * @param value value to associate with the key. + */ + void insert(const key_type& key, const value_type& value); + + /** + * Insert a new key-value pair. + * + * @param key pointer to the first character of a character array that + * stores key value. + * @param len length of the character array storing the key. + * @param value value to associate with the key. + */ + void insert(const key_unit_type* key, size_type len, const value_type& value); + + /** + * Erase a key and the value associated with it. + * + * @param key pointer to the first character of a character array that + * stores key value. + * @param len length of the character array storing the key. + * + * @return true if a key is erased, false otherwise. + */ + bool erase(const key_unit_type* key, size_type len); + + /** + * Find a value associated with a specified key. + * + * @param key key to match. + * + * @return immutable iterator that references a value associated with the + * key, or the end position in case the key is not found. + */ + const_iterator find(const key_type& key) const; + + /** + * Find a value associated with a specified key. + * + * @param input pointer to an array whose value represents the key to + * match. + * @param len length of the matching key value. + * + * @return immutable iterator that references a value associated with the + * key, or the end position in case the key is not found. + */ + const_iterator find(const key_unit_type* input, size_type len) const; + + /** + * Find a value associated with a specified key. + * + * @param key key to match. + * + * @return mutable iterator that references a value associated with the + * key, or the end position in case the key is not found. + */ + iterator find(const key_type& key); + + /** + * Find a value associated with a specified key. + * + * @param input pointer to an array whose value represents the key to + * match. + * @param len length of the matching key value. + * + * @return mutable iterator that references a value associated with the + * key, or the end position in case the key is not found. + */ + iterator find(const key_unit_type* input, size_type len); + + /** + * Retrieve all key-value pairs whose keys start with specified prefix. + * You can also retrieve all key-value pairs by passing a null prefix and + * a length of zero. + * + * @param prefix prefix to match. + * + * @return results object that contains all matching key-value pairs. The + * results are sorted by the key in ascending order. + */ + search_results prefix_search(const key_type& prefix) const; + + /** + * Retrieve all key-value pairs whose keys start with specified prefix. + * You can also retrieve all key-value pairs by passing a null prefix and + * a length of zero. + * + * @param prefix pointer to an array whose value represents the prefix to + * match. + * @param len length of the prefix value to match. + * + * @return results object that contains all matching key-value pairs. The + * results are sorted by the key in ascending order. + */ + search_results prefix_search(const key_unit_type* prefix, size_type len) const; + + /** + * Return the number of entries in the map. + * + * @return the number of entries in the map. + */ + size_type size() const; + + bool empty() const noexcept; + + /** + * Empty the container. + */ + void clear(); + + /** + * Create a compressed and immutable version of the container which + * provides better search performance and requires much less memory + * footprint. + * + * @return an instance of mdds::packed_trie_map with the same content. + */ + packed_type pack() const; + +private: + void insert_into_tree( + trie_node& node, const key_unit_type* key, const key_unit_type* key_end, const value_type& value); + + const trie_node* find_prefix_node( + const trie_node& node, const key_unit_type* prefix, const key_unit_type* prefix_end) const; + + template + void find_prefix_node_with_stack( + std::vector>& node_stack, mdds::detail::const_t& node, + const key_unit_type* prefix, const key_unit_type* prefix_end) const; + + template + key_buffer_type build_key_buffer_from_node_stack(const std::vector>& node_stack) const; + + void count_values(size_type& n, const trie_node& node) const; + +private: + trie_node m_root; +}; + +/** + * An immutable trie container that packs its content into a contiguous + * array to achieve both space efficiency and lookup performance. The user + * of this data structure must provide a pre-constructed list of key-value + * entries that are sorted by the key in ascending order, or construct from + * an instance of mdds::trie_map. + * + * Note that, since this container is immutable, the content of the + * container cannot be modified once constructed. + */ +template +class packed_trie_map +{ + friend class trie::detail::packed_iterator_base; + friend class trie::detail::packed_search_results; + +public: + typedef KeyTraits key_traits_type; + typedef typename key_traits_type::key_type key_type; + typedef typename key_traits_type::key_buffer_type key_buffer_type; + typedef typename key_traits_type::key_unit_type key_unit_type; + typedef ValueT value_type; + typedef size_t size_type; + typedef std::pair key_value_type; + typedef trie::detail::packed_iterator_base const_iterator; + typedef trie::detail::packed_search_results search_results; + + /** + * Single key-value entry. Caller must provide at compile time a static + * array of these entries. + */ + struct entry + { + const key_unit_type* key; + size_type keylen; + value_type value; + + entry(const key_unit_type* _key, size_type _keylen, value_type _value) + : key(_key), keylen(_keylen), value(_value) + {} + }; + +private: + struct trie_node + { + key_unit_type key; + const value_type* value; + + std::deque children; + + trie_node(key_unit_type _key) : key(_key), value(nullptr) + {} + }; + + struct stack_item + { + const uintptr_t* node_pos; + const uintptr_t* child_pos; + const uintptr_t* child_end; + + stack_item(const uintptr_t* _node_pos, const uintptr_t* _child_pos, const uintptr_t* _child_end) + : node_pos(_node_pos), child_pos(_child_pos), child_end(_child_end) + {} + + bool operator==(const stack_item& other) const + { + return node_pos == other.node_pos && child_pos == other.child_pos; + } + + bool operator!=(const stack_item& other) const + { + return !operator==(other); + } + + bool has_value() const + { + const value_type* pv = reinterpret_cast(*node_pos); + return pv; + } + + const value_type* get_value() const + { + return reinterpret_cast(*node_pos); + } + }; + + typedef std::vector node_stack_type; + + typedef std::deque node_pool_type; + typedef std::vector packed_type; + typedef std::deque value_store_type; + typedef std::vector> child_offsets_type; + +public: + packed_trie_map(); + + /** + * Constructor that initializes the content from a static list of + * key-value entries. The caller must ensure that the key-value + * entries are sorted in ascending order, else the behavior is undefined. + * + * @param entries pointer to the array of key-value entries. + * @param entry_size size of the key-value entry array. + */ + packed_trie_map(const entry* entries, size_type entry_size); + + /** + * Constructor to allow construction of an instance from the content of a + * mdds::trie_map instance. + * + * @param other mdds::trie_map instance to build content from. + */ + packed_trie_map(const trie_map& other); + + packed_trie_map(const packed_trie_map& other); + + packed_trie_map(packed_trie_map&& other); + + packed_trie_map& operator=(packed_trie_map other); + + bool operator==(const packed_trie_map& other) const; + + bool operator!=(const packed_trie_map& other) const; + + const_iterator begin() const; + + const_iterator end() const; + + const_iterator cbegin() const; + + const_iterator cend() const; + + /** + * Find a value associated with a specified key. + * + * @param key key to match. + * + * @return iterator that references a value associated with the key, or + * the end position in case the key is not found. + */ + const_iterator find(const key_type& key) const; + + /** + * Find a value associated with a specified key. + * + * @param input pointer to an array whose value represents the key to + * match. + * @param len length of the matching key value. + * + * @return iterator that references a value associated with the key, or + * the end position in case the key is not found. + */ + const_iterator find(const key_unit_type* input, size_type len) const; + + /** + * Retrieve all key-value pairs whose keys start with specified prefix. + * You can also retrieve all key-value pairs by passing a null prefix and + * a length of zero. + * + * @param prefix prefix to match. + * + * @return results object containing all matching key-value pairs. + */ + search_results prefix_search(const key_type& prefix) const; + + /** + * Retrieve all key-value pairs whose keys start with specified prefix. + * You can also retrieve all key-value pairs by passing a null prefix and + * a length of zero. + * + * @param prefix pointer to an array whose value represents the prefix to + * match. + * @param len length of the prefix value to match. + * + * @return results object that contains all matching key-value pairs. The + * results are sorted by the key in ascending order. + */ + search_results prefix_search(const key_unit_type* prefix, size_type len) const; + + /** + * Return the number of entries in the map. + * + * @return the number of entries in the map. + */ + size_type size() const noexcept; + + bool empty() const noexcept; + + void swap(packed_trie_map& other); + + /** + * Save the state of the instance of this class to an external buffer. + * + * @param os output stream to write the state to. + */ + template> + void save_state(std::ostream& os) const; + + /** + * Restore the state of the instance of this class from an external + * buffer. + * + * @param is input stream to load the state from. + */ + template> + void load_state(std::istream& is); + + /** + * Dump the structure of the trie content for debugging. You must define + * MDDS_TRIE_MAP_DEBUG in order for this method to generate + * output. + */ + void dump_structure() const; + +private: + node_stack_type get_root_stack() const; + + void traverse_range( + trie_node& root, node_pool_type& node_pool, const entry* start, const entry* end, size_type pos); + + size_type compact_node(const trie_node& node); + size_type compact_node(const typename trie_map::trie_node& node); + + void push_child_offsets(size_type offset, const child_offsets_type& child_offsets); + + void compact(const trie_node& root); + void compact(const typename trie_map::trie_node& root); + + const uintptr_t* find_prefix_node( + const uintptr_t* p, const key_unit_type* prefix, const key_unit_type* prefix_end) const; + + void find_prefix_node_with_stack( + node_stack_type& node_stack, const uintptr_t* p, const key_unit_type* prefix, + const key_unit_type* prefix_end) const; + + template + void traverse_tree(_Handler hdl) const; + + template + void traverse_buffer(_Handler hdl) const; + +#ifdef MDDS_TRIE_MAP_DEBUG + void dump_node(key_buffer_type& buffer, const trie_node& node) const; + void dump_trie(const trie_node& root) const; + void dump_packed_trie() const; +#endif + +private: + value_store_type m_value_store; + packed_type m_packed; +}; + +} // namespace mdds + +#include "trie_map_def.inl" + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/trie_map_def.inl b/include/mdds/trie_map_def.inl new file mode 100644 index 0000000..e716469 --- /dev/null +++ b/include/mdds/trie_map_def.inl @@ -0,0 +1,1735 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * Copyright (c) 2015 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#include "mdds/global.hpp" + +#include +#include +#include +#include +#include +#include + +#ifdef MDDS_TRIE_MAP_DEBUG +#include +#include +using std::cout; +using std::endl; +#endif + +namespace mdds { + +namespace detail { namespace trie { + +inline const char* value_type_size_name(bool variable_size) +{ + return variable_size ? "varaible size" : "fixed size"; +} + +union bin_value +{ + char buffer[8]; + uint8_t ui8; + uint16_t ui16; + uint32_t ui32; + uint64_t ui64; +}; + +using value_addrs_type = std::map; + +template +struct write_variable_size_values_to_ostream +{ + value_addrs_type operator()(std::ostream& os, const std::deque& value_store) const + { + bin_value bv; + + value_addrs_type value_addrs; + + size_t pos = 0; + for (const ValueT& v : value_store) + { + auto sp_size = os.tellp(); // position to come back to to write the size. + bv.ui32 = 0; + os.write(bv.buffer, 4); // write 0 as a placeholder. + + auto sp_start = os.tellp(); + FuncT::write(os, v); + auto sp_end = os.tellp(); + + bv.ui32 = sp_end - sp_start; // bytes written + + // go back and write the actual bytes written. + os.seekp(sp_size); + os.write(bv.buffer, 4); + os.seekp(sp_end); + + value_addrs.insert({&v, pos++}); + } + + return value_addrs; + } +}; + +template +struct write_fixed_size_values_to_ostream +{ + value_addrs_type operator()(std::ostream& os, const std::deque& value_store) const + { + bin_value bv; + value_addrs_type value_addrs; + + // Write the size of constant-size values. + bv.ui32 = FuncT::value_size; + os.write(bv.buffer, 4); + + size_t pos = 0; + for (const ValueT& v : value_store) + { + auto sp_start = os.tellp(); + FuncT::write(os, v); + auto sp_end = os.tellp(); + + size_t bytes_written = sp_end - sp_start; + if (bytes_written != FuncT::value_size) + { + std::ostringstream msg; + msg << "bytes written (" << bytes_written << ") does not equal the value size (" << FuncT::value_size + << ")"; + throw size_error(msg.str()); + } + + value_addrs.insert({&v, pos++}); + } + + return value_addrs; + } +}; + +template +struct write_values_to_ostream; + +template +struct write_values_to_ostream : write_variable_size_values_to_ostream +{ +}; + +template +struct write_values_to_ostream : write_fixed_size_values_to_ostream +{ +}; + +template +struct read_fixed_size_values_from_istream +{ + using value_store_type = std::deque; + + value_store_type operator()(std::istream& is, uint32_t value_count) const + { + value_store_type value_store; + bin_value bv; + + // read the size of the value. + is.read(bv.buffer, 4); + size_t size = bv.ui32; + + if (size != FuncT::value_size) + { + std::ostringstream os; + os << "wrong size of fixed value type (expected: " << FuncT::value_size << "; actual: " << size << ")"; + throw std::invalid_argument(os.str()); + } + + for (uint32_t i = 0; i < value_count; ++i) + { + value_store.emplace_back(); + FuncT::read(is, size, value_store.back()); + } + + return value_store; + } +}; + +template +struct read_variable_size_values_from_istream +{ + using value_store_type = std::deque; + + value_store_type operator()(std::istream& is, uint32_t value_count) const + { + value_store_type value_store; + bin_value bv; + + for (uint32_t i = 0; i < value_count; ++i) + { + is.read(bv.buffer, 4); + size_t size = bv.ui32; + + ValueT v; + FuncT::read(is, size, v); + + value_store.push_back(std::move(v)); + } + + return value_store; + } +}; + +template +struct read_values_from_istream; + +template +struct read_values_from_istream : read_variable_size_values_from_istream +{ +}; + +template +struct read_values_from_istream : read_fixed_size_values_from_istream +{ +}; + +}} // namespace detail::trie + +namespace trie { + +template +void numeric_value_serializer::write(std::ostream& os, const T& v) +{ + static_assert(std::is_arithmetic::value, "not a numeric type."); + + constexpr size_t s = sizeof(T); + const char* p = reinterpret_cast(&v); + os.write(p, s); +} + +template +void numeric_value_serializer::read(std::istream& is, size_t n, T& v) +{ + static_assert(std::is_arithmetic::value, "not a numeric type."); + + constexpr size_t s = sizeof(T); + assert(s == n); + + union + { + char buffer[s]; + T v; + + } buf; + + char* p = buf.buffer; + + while (n) + { + is.read(p, s); + auto size_read = is.gcount(); + n -= size_read; + p += size_read; + } + + v = buf.v; +} + +template +void numeric_sequence_value_serializer::write(std::ostream& os, const T& v) +{ + static_assert( + std::is_arithmetic::value, "value type of this vector is not a numeric type."); + + for (const auto& elem : v) + element_serializer::write(os, elem); +} + +template +void numeric_sequence_value_serializer::read(std::istream& is, size_t n, T& v) +{ + using elem_type = typename T::value_type; + static_assert( + std::is_arithmetic::value, "value type of this vector is not a numeric type."); + + constexpr size_t elem_size = element_serializer::value_size; + assert(n % elem_size == 0); + + size_t elem_count = n / elem_size; + + for (size_t i = 0; i < elem_count; ++i) + { + elem_type elem; + element_serializer::read(is, elem_size, elem); + v.push_back(elem); + } +} + +template<> +inline void variable_value_serializer::write(std::ostream& os, const std::string& v) +{ + os.write(v.data(), v.size()); +} + +template<> +inline void variable_value_serializer::read(std::istream& is, size_t n, std::string& v) +{ + v.resize(n); + char* p = const_cast(v.data()); + + while (n) + { + is.read(p, n); + auto size_read = is.gcount(); + n -= size_read; + p += size_read; + } +} + +} // namespace trie + +template +trie_map::trie_map::trie_node::trie_node() : value(value_type()), has_value(false) +{} + +template +trie_map::trie_map::trie_node::trie_node(const trie_node& other) + : children(other.children), value(other.value), has_value(other.has_value) +{} + +template +trie_map::trie_map::trie_node::trie_node(trie_node&& other) + : children(std::move(other.children)), value(std::move(other.value)), has_value(std::move(other.has_value)) +{} + +template +void trie_map::trie_map::trie_node::swap(trie_node& other) +{ + children.swap(other.children); + std::swap(value, other.value); + std::swap(has_value, other.has_value); +} + +template +trie_map::trie_map() +{} + +template +trie_map::trie_map(const trie_map& other) : m_root(other.m_root) +{} + +template +trie_map::trie_map(trie_map&& other) : m_root(std::move(other.m_root)) +{} + +template +typename trie_map::const_iterator trie_map::begin() const +{ + if (m_root.children.empty()) + // empty container + return end(); + + // Push the root node. + key_buffer_type buf; + const_node_stack_type node_stack; + node_stack.emplace_back(&m_root, m_root.children.begin()); + + // Push root's first child node. + auto it = node_stack.back().child_pos; + const_iterator::push_child_node_to_stack(node_stack, buf, it); + + // In theory there should always be at least one value node along the + // left-most branch. + + while (!node_stack.back().node->has_value) + { + auto this_it = node_stack.back().child_pos; + const_iterator::push_child_node_to_stack(node_stack, buf, this_it); + } + + return const_iterator(std::move(node_stack), std::move(buf), trie::detail::iterator_type::normal); +} + +template +typename trie_map::iterator trie_map::begin() +{ + if (m_root.children.empty()) + // empty container + return end(); + + // Push the root node. + key_buffer_type buf; + node_stack_type node_stack; + node_stack.emplace_back(&m_root, m_root.children.begin()); + + // Push root's first child node. + auto it = node_stack.back().child_pos; + iterator::push_child_node_to_stack(node_stack, buf, it); + + // In theory there should always be at least one value node along the + // left-most branch. + + while (!node_stack.back().node->has_value) + { + auto this_it = node_stack.back().child_pos; + iterator::push_child_node_to_stack(node_stack, buf, this_it); + } + + return iterator(std::move(node_stack), std::move(buf), trie::detail::iterator_type::normal); +} + +template +typename trie_map::const_iterator trie_map::end() const +{ + const_node_stack_type node_stack; + node_stack.emplace_back(&m_root, m_root.children.end()); + return const_iterator(std::move(node_stack), key_buffer_type(), trie::detail::iterator_type::end); +} + +template +typename trie_map::iterator trie_map::end() +{ + node_stack_type node_stack; + node_stack.emplace_back(&m_root, m_root.children.end()); + return iterator(std::move(node_stack), key_buffer_type(), trie::detail::iterator_type::end); +} + +template +trie_map& trie_map::operator=(trie_map other) +{ + trie_map tmp(std::move(other)); + tmp.swap(*this); + return *this; +} + +template +void trie_map::swap(trie_map& other) +{ + m_root.swap(other.m_root); +} + +template +void trie_map::insert(const key_type& key, const value_type& value) +{ + using ktt = key_traits_type; + + key_buffer_type buf = ktt::to_key_buffer(key); + const key_unit_type* p = ktt::buffer_data(buf); + size_t n = ktt::buffer_size(buf); + const key_unit_type* p_end = p + n; + insert_into_tree(m_root, p, p_end, value); +} + +template +void trie_map::insert(const key_unit_type* key, size_type len, const value_type& value) +{ + const key_unit_type* key_end = key + len; + insert_into_tree(m_root, key, key_end, value); +} + +template +bool trie_map::erase(const key_unit_type* key, size_type len) +{ + const key_unit_type* key_end = key + len; + + const_node_stack_type node_stack; + find_prefix_node_with_stack(node_stack, m_root, key, key_end); + + if (node_stack.empty() || !node_stack.back().node->has_value) + // Nothing is erased. + return false; + + const trie_node* node = node_stack.back().node; + trie_node* node_mod = const_cast(node); + node_mod->has_value = false; + + // If this is a leaf node, remove it, and keep removing its parents until + // we reach a parent node that still has at least one child node. + + while (!node->has_value && node->children.empty() && node_stack.size() > 1) + { + node_stack.pop_back(); + auto& si = node_stack.back(); + + const_cast(si.node)->children.erase(si.child_pos); + node = si.node; + } + + return true; +} + +template +void trie_map::insert_into_tree( + trie_node& node, const key_unit_type* key, const key_unit_type* key_end, const value_type& value) +{ + if (key == key_end) + { + node.value = value; + node.has_value = true; + return; + } + + key_unit_type c = *key; + + auto it = node.children.lower_bound(c); + if (it == node.children.end() || node.children.key_comp()(c, it->first)) + { + // Insert a new node. + it = node.children.insert(it, typename trie_node::children_type::value_type(c, trie_node())); + } + + ++key; + insert_into_tree(it->second, key, key_end, value); +} + +template +const typename trie_map::trie_node* trie_map::find_prefix_node( + const trie_node& node, const key_unit_type* prefix, const key_unit_type* prefix_end) const +{ + if (prefix == prefix_end) + // Right node is found. + return &node; + + auto it = node.children.find(*prefix); + if (it == node.children.end()) + return nullptr; + + ++prefix; + return find_prefix_node(it->second, prefix, prefix_end); +} + +template +template +void trie_map::find_prefix_node_with_stack( + std::vector>& node_stack, mdds::detail::const_t& node, + const key_unit_type* prefix, const key_unit_type* prefix_end) const +{ + if (prefix == prefix_end) + { + // Right node is found. + node_stack.emplace_back(&node, node.children.begin()); + return; + } + + auto it = node.children.find(*prefix); + if (it == node.children.end()) + return; + + node_stack.emplace_back(&node, it); + + ++prefix; + find_prefix_node_with_stack(node_stack, it->second, prefix, prefix_end); +} + +template +template +typename trie_map::key_buffer_type trie_map::build_key_buffer_from_node_stack( + const std::vector>& node_stack) const +{ + // Build the key value from the stack. + key_buffer_type buf; + auto end = node_stack.end(); + --end; // Skip the node with value which doesn't store a key element. + std::for_each(node_stack.begin(), end, [&](const stack_item& si) { + using ktt = key_traits_type; + ktt::push_back(buf, si.child_pos->first); + }); + + return buf; +} + +template +void trie_map::count_values(size_type& n, const trie_node& node) const +{ + if (node.has_value) + ++n; + + std::for_each( + node.children.begin(), node.children.end(), + [&](const typename trie_node::children_type::value_type& v) { count_values(n, v.second); }); +} + +template +typename trie_map::const_iterator trie_map::find(const key_type& key) const +{ + using ktt = key_traits_type; + key_buffer_type buf = ktt::to_key_buffer(key); + const key_unit_type* p = ktt::buffer_data(buf); + size_t n = ktt::buffer_size(buf); + + return find(p, n); +} + +template +typename trie_map::const_iterator trie_map::find( + const key_unit_type* input, size_type len) const +{ + const key_unit_type* input_end = input + len; + const_node_stack_type node_stack; + find_prefix_node_with_stack(node_stack, m_root, input, input_end); + if (node_stack.empty() || !node_stack.back().node->has_value) + // Specified key doesn't exist. + return end(); + + key_buffer_type buf = build_key_buffer_from_node_stack(node_stack); + + return const_iterator(std::move(node_stack), std::move(buf), trie::detail::iterator_type::normal); +} + +template +typename trie_map::iterator trie_map::find(const key_type& key) +{ + using ktt = key_traits_type; + key_buffer_type buf = ktt::to_key_buffer(key); + const key_unit_type* p = ktt::buffer_data(buf); + size_t n = ktt::buffer_size(buf); + + return find(p, n); +} + +template +typename trie_map::iterator trie_map::find( + const key_unit_type* input, size_type len) +{ + const key_unit_type* input_end = input + len; + node_stack_type node_stack; + find_prefix_node_with_stack(node_stack, m_root, input, input_end); + if (node_stack.empty() || !node_stack.back().node->has_value) + // Specified key doesn't exist. + return end(); + + key_buffer_type buf = build_key_buffer_from_node_stack(node_stack); + + return iterator(std::move(node_stack), std::move(buf), trie::detail::iterator_type::normal); +} + +template +typename trie_map::search_results trie_map::prefix_search( + const key_type& key) const +{ + using ktt = key_traits_type; + key_buffer_type buf = ktt::to_key_buffer(key); + const key_unit_type* p = ktt::buffer_data(buf); + size_t n = ktt::buffer_size(buf); + + return prefix_search(p, n); +} + +template +typename trie_map::search_results trie_map::prefix_search( + const key_unit_type* prefix, size_type len) const +{ + using ktt = key_traits_type; + + const key_unit_type* prefix_end = prefix + len; + std::vector matches; + + const trie_node* node = find_prefix_node(m_root, prefix, prefix_end); + return search_results(node, ktt::to_key_buffer(prefix, len)); +} + +template +typename trie_map::size_type trie_map::size() const +{ + size_type n = 0; + count_values(n, m_root); + return n; +} + +template +bool trie_map::empty() const noexcept +{ + return m_root.children.empty() && !m_root.has_value; +} + +template +void trie_map::clear() +{ + m_root.children.clear(); + m_root.has_value = false; +} + +template +typename trie_map::packed_type trie_map::pack() const +{ + return packed_type(*this); +} + +#ifdef MDDS_TRIE_MAP_DEBUG + +template +void packed_trie_map::dump_node(key_buffer_type& buffer, const trie_node& node) const +{ + using namespace std; + + using ktt = key_traits_type; + + if (node.value) + { + // This node has value. + cout << buffer << ":" << *static_cast(node.value) << endl; + } + + std::for_each(node.children.begin(), node.children.end(), [&](const trie_node* p) { + const trie_node& this_node = *p; + ktt::push_back(buffer, this_node.key); + dump_node(buffer, this_node); + ktt::pop_back(buffer); + }); +} + +template +void packed_trie_map::dump_trie(const trie_node& root) const +{ + key_buffer_type buffer; + dump_node(buffer, root); +} + +template +void packed_trie_map::dump_packed_trie() const +{ + cout << "packed size: " << m_packed.size() << endl; + + struct _handler + { + size_t this_node_offset = 0; + + /** first element in the buffer. */ + void root_offset(size_t i, const uintptr_t& v) const + { + cout << std::setw(4) << i << ": root node offset: " << v << endl; + } + + /** first element in each node section. */ + void node_value(size_t i, const uintptr_t& v) + { + this_node_offset = i; + const value_type* value = reinterpret_cast(v); + cout << std::setw(4) << i << ": node value pointer: " << value << endl; + } + + /** + * second element in each node section that stores the size of + * the child data sub-section. + */ + void node_index_size(size_t i, const uintptr_t& v) const + { + cout << std::setw(4) << i << ": index size: " << size_t(v) << endl; + } + + /** element that stores the key value for child node. */ + void node_child_key(size_t i, const uintptr_t& v) const + { + key_unit_type key = v; + cout << std::setw(4) << i << ": key: " << key << endl; + } + + /** element that stores the relative offset of the child node. */ + void node_child_offset(size_t i, const uintptr_t& v) const + { + size_t offset = v; + cout << std::setw(4) << i << ": offset: " << offset << " (abs: " << (this_node_offset - offset) << ")" + << endl; + } + + } handler; + + traverse_buffer(handler); +} + +#endif + +template +void packed_trie_map::traverse_range( + trie_node& root, node_pool_type& node_pool, const typename packed_trie_map::entry* start, + const typename packed_trie_map::entry* end, size_type pos) +{ + const entry* p = start; + const entry* range_start = start; + const entry* range_end = nullptr; + key_unit_type range_char = 0; + size_type range_count = 0; + + for (; p != end; ++p) + { + if (pos > p->keylen) + continue; + + if (pos == p->keylen) + { + root.value = &p->value; + continue; + } + + ++range_count; + key_unit_type c = p->key[pos]; + + if (!range_char) + range_char = c; + else if (range_char != c) + { + // End of current character range. + range_end = p; + + node_pool.emplace_back(range_char); + root.children.push_back(&node_pool.back()); + traverse_range(*root.children.back(), node_pool, range_start, range_end, pos + 1); + range_start = range_end; + range_char = range_start->key[pos]; + range_end = nullptr; + range_count = 1; + } + } + + if (range_count) + { + assert(range_char); + node_pool.emplace_back(range_char); + root.children.push_back(&node_pool.back()); + traverse_range(*root.children.back(), node_pool, range_start, end, pos + 1); + } +} + +template +typename packed_trie_map::size_type packed_trie_map::compact_node( + const trie_node& node) +{ + std::vector> child_offsets; + child_offsets.reserve(node.children.size()); + + // Process child nodes first. + std::for_each(node.children.begin(), node.children.end(), [&](const trie_node* p) { + const trie_node& child_node = *p; + size_type child_offset = compact_node(child_node); + child_offsets.emplace_back(child_offset, child_node.key); + }); + + // Process this node. + size_type offset = m_packed.size(); + if (node.value) + { + m_value_store.push_back(*node.value); // copy the value object. + m_packed.push_back(uintptr_t(&m_value_store.back())); + } + else + m_packed.push_back(uintptr_t(0)); + + push_child_offsets(offset, child_offsets); + return offset; +} + +template +typename packed_trie_map::size_type packed_trie_map::compact_node( + const typename trie_map::trie_node& node) +{ + using node_type = typename trie_map::trie_node; + + std::vector> child_offsets; + child_offsets.reserve(node.children.size()); + + // Process child nodes first. + std::for_each( + node.children.begin(), node.children.end(), [&](const typename node_type::children_type::value_type& v) { + key_unit_type key = v.first; + const node_type& child_node = v.second; + size_type child_offset = compact_node(child_node); + child_offsets.emplace_back(child_offset, key); + }); + + // Process this node. + size_type offset = m_packed.size(); + if (node.has_value) + { + m_value_store.push_back(node.value); // copy the value object. + m_packed.push_back(uintptr_t(&m_value_store.back())); + } + else + m_packed.push_back(uintptr_t(0)); + + push_child_offsets(offset, child_offsets); + return offset; +} + +template +void packed_trie_map::push_child_offsets(size_type offset, const child_offsets_type& child_offsets) +{ + m_packed.push_back(uintptr_t(child_offsets.size() * 2)); + + std::for_each(child_offsets.begin(), child_offsets.end(), [&](const std::tuple& v) { + key_unit_type key = std::get<1>(v); + size_t child_offset = std::get<0>(v); + m_packed.push_back(key); + m_packed.push_back(offset - child_offset); + }); +} + +template +void packed_trie_map::compact(const trie_node& root) +{ + packed_type init(size_t(1), uintptr_t(0)); + m_packed.swap(init); + assert(m_packed.size() == 1); + + size_t root_offset = compact_node(root); + m_packed[0] = root_offset; +} + +template +void packed_trie_map::compact(const typename trie_map::trie_node& root) +{ + packed_type init(size_t(1), uintptr_t(0)); + m_packed.swap(init); + assert(m_packed.size() == 1); + + size_t root_offset = compact_node(root); + m_packed[0] = root_offset; +} + +template +packed_trie_map::packed_trie_map() : m_packed(3, 0u) +{ + // root offset (0), root value (1), and root child count (2). + m_packed[0] = 1; +} + +template +packed_trie_map::packed_trie_map(const entry* entries, size_type entry_size) +{ + const entry* p = entries; + const entry* p_end = p + entry_size; + +#if defined(MDDS_TRIE_MAP_DEBUG) + // Make sure the entries really are sorted. + auto func_compare = [](const entry& left, const entry& right) -> bool { + size_type n_key = std::min(left.keylen, right.keylen); + int ret = std::memcmp(left.key, right.key, n_key); + if (ret == 0) + return left.keylen < right.keylen; + + return ret < 0; + }; + + if (!std::is_sorted(p, p_end, func_compare)) + throw integrity_error("the list of entries is not sorted."); + +#endif + + // Populate the normal tree first. + trie_node root(0); + node_pool_type node_pool; + traverse_range(root, node_pool, p, p_end, 0); +#if defined(MDDS_TRIE_MAP_DEBUG) && defined(MDDS_TRIE_MAP_DEBUG_DUMP_TRIE) + dump_trie(root); +#endif + + // Compact the trie into a packed array. + compact(root); +#if defined(MDDS_TRIE_MAP_DEBUG) && defined(MDDS_TRIE_MAP_DEBUG_DUMP_PACKED) + dump_packed_trie(); +#endif +} + +template +packed_trie_map::packed_trie_map(const trie_map& other) +{ + compact(other.m_root); +} + +template +packed_trie_map::packed_trie_map(const packed_trie_map& other) : m_packed(other.m_packed) +{ + struct _handler + { + packed_trie_map& m_parent; + + void node(const uintptr_t* node_pos, key_unit_type /*c*/, size_t /*depth*/, size_t /*index_size*/) + { + uintptr_t value_ptr = *node_pos; + + if (value_ptr) + { + auto p = reinterpret_cast(value_ptr); + m_parent.m_value_store.push_back(*p); // copy the value object. + const uintptr_t* head = m_parent.m_packed.data(); + size_t offset = std::distance(head, node_pos); + m_parent.m_packed[offset] = uintptr_t(&m_parent.m_value_store.back()); + } + } + + void move_up(const uintptr_t*, const uintptr_t*, const uintptr_t*) + {} + + void next_child() + {} + + void end() + {} + + _handler(packed_trie_map& parent) : m_parent(parent) + {} + + } handler(*this); + + traverse_tree(handler); +} + +template +packed_trie_map::packed_trie_map(packed_trie_map&& other) + : m_value_store(std::move(other.m_value_store)), m_packed(std::move(other.m_packed)) +{ + // Even the empty structure needs to have the root offset and the empty root record. + other.m_packed.resize(3, 0u); // root offset (0), root value (1), and root child count (2). + other.m_packed[0] = 1; +} + +template +packed_trie_map& packed_trie_map::operator=(packed_trie_map other) +{ + packed_trie_map tmp(std::move(other)); + tmp.swap(*this); + return *this; +} + +template +bool packed_trie_map::operator==(const packed_trie_map& other) const +{ + if (m_value_store.size() != other.m_value_store.size()) + return false; + + // Since the two containers are of the same size, the iterator ranges should + // be the same as well. + auto left = cbegin(), right = other.cbegin(); + for (; left != cend(); ++left, ++right) + { + assert(right != other.cend()); + if (*left != *right) + return false; + } + + return true; +} + +template +bool packed_trie_map::operator!=(const packed_trie_map& other) const +{ + return !operator==(other); +} + +template +typename packed_trie_map::const_iterator packed_trie_map::begin() const +{ + return cbegin(); +} + +template +typename packed_trie_map::const_iterator packed_trie_map::end() const +{ + return cend(); +} + +template +typename packed_trie_map::const_iterator packed_trie_map::cbegin() const +{ + using ktt = key_traits_type; + + node_stack_type node_stack = get_root_stack(); + + const stack_item* si = &node_stack.back(); + if (si->child_pos == si->child_end) + // empty container. + return const_iterator(std::move(node_stack), key_buffer_type()); + + const uintptr_t* node_pos = si->node_pos; + const uintptr_t* child_pos = si->child_pos; + const uintptr_t* child_end = si->child_end; + const uintptr_t* p = child_pos; + + // Follow the root node's left-most child. + key_buffer_type buf; + key_unit_type c = *p; + ktt::push_back(buf, c); + ++p; + size_t offset = *p; + + node_pos -= offset; // jump to the child node. + p = node_pos; + ++p; + size_t index_size = *p; + ++p; + child_pos = p; + child_end = child_pos + index_size; + + // Push this child node onto the stack. + node_stack.emplace_back(node_pos, child_pos, child_end); + + const value_type* pv = reinterpret_cast(*node_pos); + while (!pv) + { + // Keep following the left child node until we reach a node with value. + const_iterator::push_child_node_to_stack(node_stack, buf, node_stack.back().child_pos); + pv = reinterpret_cast(*node_stack.back().node_pos); + } + + return const_iterator(std::move(node_stack), std::move(buf), *pv); +} + +template +typename packed_trie_map::const_iterator packed_trie_map::cend() const +{ + node_stack_type node_stack = get_root_stack(); + node_stack.back().child_pos = node_stack.back().child_end; + return const_iterator(std::move(node_stack), key_buffer_type()); +} + +template +typename packed_trie_map::node_stack_type packed_trie_map::get_root_stack() const +{ + assert(!m_packed.empty()); + size_t root_offset = m_packed[0]; + assert(root_offset < m_packed.size()); + const uintptr_t* p = m_packed.data() + root_offset; + const uintptr_t* node_pos = p; + ++p; + size_t index_size = *p; + ++p; + const uintptr_t* child_pos = p; + const uintptr_t* child_end = child_pos + index_size; + + node_stack_type node_stack; + node_stack.emplace_back(node_pos, child_pos, child_end); + + return node_stack; +} + +template +typename packed_trie_map::const_iterator packed_trie_map::find( + const key_type& key) const +{ + using ktt = key_traits_type; + key_buffer_type buf = ktt::to_key_buffer(key); + const key_unit_type* p = ktt::buffer_data(buf); + size_t n = ktt::buffer_size(buf); + + return find(p, n); +} + +template +typename packed_trie_map::const_iterator packed_trie_map::find( + const key_unit_type* input, size_type len) const +{ + if (m_value_store.empty()) + return end(); + + const key_unit_type* key_end = input + len; + size_t root_offset = m_packed[0]; + assert(root_offset < m_packed.size()); + const uintptr_t* root = m_packed.data() + root_offset; + + node_stack_type node_stack; + find_prefix_node_with_stack(node_stack, root, input, key_end); + if (node_stack.empty() || !node_stack.back().node_pos) + return end(); + + const stack_item& si = node_stack.back(); + const value_type* pv = reinterpret_cast(*si.node_pos); + if (!pv) + return end(); + + // Build the key value from the stack. + key_buffer_type buf; + auto end = node_stack.end(); + --end; // Skip the node with value which doesn't store a key element. + std::for_each(node_stack.begin(), end, [&](const stack_item& this_si) { + using ktt = key_traits_type; + ktt::push_back(buf, *this_si.child_pos); + }); + + return const_iterator(std::move(node_stack), std::move(buf), *pv); +} + +template +typename packed_trie_map::search_results packed_trie_map::prefix_search( + const key_type& key) const +{ + using ktt = key_traits_type; + key_buffer_type buf = ktt::to_key_buffer(key); + const key_unit_type* p = ktt::buffer_data(buf); + size_t n = ktt::buffer_size(buf); + + return prefix_search(p, n); +} + +template +typename packed_trie_map::search_results packed_trie_map::prefix_search( + const key_unit_type* prefix, size_type len) const +{ + using ktt = key_traits_type; + + if (m_value_store.empty()) + return search_results(nullptr, key_buffer_type()); + + const key_unit_type* prefix_end = prefix + len; + + size_t root_offset = m_packed[0]; + assert(root_offset < m_packed.size()); + const uintptr_t* root = m_packed.data() + root_offset; + + const uintptr_t* node = find_prefix_node(root, prefix, prefix_end); + key_buffer_type buf = ktt::to_key_buffer(prefix, len); + return search_results(node, std::move(buf)); +} + +template +typename packed_trie_map::size_type packed_trie_map::size() const noexcept +{ + return m_value_store.size(); +} + +template +bool packed_trie_map::empty() const noexcept +{ + return m_value_store.empty(); +} + +template +void packed_trie_map::swap(packed_trie_map& other) +{ + m_value_store.swap(other.m_value_store); + m_packed.swap(other.m_packed); +} + +template +template +void packed_trie_map::save_state(std::ostream& os) const +{ + detail::trie::bin_value bv; + + bv.ui16 = 0x0000; // write 2-byte flags + bv.ui16 |= (0x0001 & FuncT::variable_size); + os.write(bv.buffer, 2); + + // Write the number of values (4-bytes). + bv.ui32 = m_value_store.size(); + os.write(bv.buffer, 4); + + using value_addrs_type = std::map; + value_addrs_type value_addrs; + + // Dump the stored values first. + using value_size_type = std::bool_constant; + + detail::trie::write_values_to_ostream func; + value_addrs = func(os, m_value_store); + + // Write 0xFF to signify the end of the value section. + bv.ui8 = 0xFF; + os.write(bv.buffer, 1); + + // Write the size of uintptr_t. It must be either 4 or 8. + bv.ui8 = sizeof(uintptr_t); + os.write(bv.buffer, 1); + + // Write the size of the packed blob. + bv.ui64 = m_packed.size(); + os.write(bv.buffer, 8); + + struct _handler + { + size_t const m_elem_size = sizeof(uintptr_t); + std::ostream& m_os; + const value_addrs_type& m_value_addrs; + const packed_trie_map& m_parent; + + inline void write(uintptr_t v) const + { + const char* p = reinterpret_cast(&v); + m_os.write(p, m_elem_size); + } + + /** first element in the buffer. */ + void root_offset(size_t /*i*/, const uintptr_t& v) const + { + write(v); + } + + /** first element in each node section. */ + void node_value(size_t /*i*/, const uintptr_t& v) const + { + const value_type* p = reinterpret_cast(v); + if (p) + { + // Replace the pointer of the value with its index into the value store. + auto it = m_value_addrs.find(p); + assert(it != m_value_addrs.cend()); + uintptr_t index = it->second; + + write(index); + } + else + { + // Use the numeric value with all bits set, to encode 0. + // Because the index of the value is 0-based, we can't just + // leave it as zero. + uintptr_t max_bits = 0; + max_bits = ~max_bits; + write(max_bits); + } + } + + /** + * second element in each node section that stores the size of + * the child data sub-section. + */ + void node_index_size(size_t /*i*/, const uintptr_t& v) const + { + write(v); + } + + /** element that stores the key value for child node. */ + void node_child_key(size_t /*i*/, const uintptr_t& v) const + { + write(v); + } + + /** element that stores the relative offset of the child node. */ + void node_child_offset(size_t /*i*/, const uintptr_t& v) const + { + write(v); + } + + _handler(std::ostream& os, const value_addrs_type& value_addrs, const packed_trie_map& parent) + : m_os(os), m_value_addrs(value_addrs), m_parent(parent) + {} + + } handler(os, value_addrs, *this); + + traverse_buffer(handler); + + // Write 0xFF to signify the end of the packed blob. + bv.ui8 = 0xFF; + os.write(bv.buffer, 1); +} + +template +template +void packed_trie_map::load_state(std::istream& is) +{ + detail::trie::bin_value bv; + is.read(bv.buffer, 2); + + uint16_t flags = bv.ui16; + bool variable_size = (flags & 0x0001) != 0; + + if (variable_size != FuncT::variable_size) + { + std::ostringstream os; + os << "This stream is meant for a value type of " << detail::trie::value_type_size_name(variable_size) + << ", but the actual value type is of " << detail::trie::value_type_size_name(FuncT::variable_size) << "."; + throw std::invalid_argument(os.str()); + } + + // read the number of values + is.read(bv.buffer, 4); + uint32_t value_count = bv.ui32; + + using value_size_type = std::bool_constant; + detail::trie::read_values_from_istream func; + m_value_store = func(is, value_count); + + // There should be a check byte of 0xFF. + is.read(bv.buffer, 1); + if (bv.ui8 != 0xFF) + throw std::invalid_argument("failed to find the check byte at the end of the value section."); + + // Size of uintptr_t + is.read(bv.buffer, 1); + size_t ptr_size = bv.ui8; + + if (ptr_size != sizeof(uintptr_t)) + throw std::invalid_argument("size of uintptr_t is different."); + + // Size of the packed blob. + is.read(bv.buffer, 8); + + size_t n = bv.ui64; + packed_type packed; + packed.reserve(n); + + for (size_t i = 0; i < n; ++i) + { + is.read(bv.buffer, sizeof(uintptr_t)); + const uintptr_t* p = reinterpret_cast(bv.buffer); + packed.push_back(*p); + } + + // the last byte must be 0xFF. + is.read(bv.buffer, 1); + if (bv.ui8 != 0xFF) + throw std::invalid_argument("failed to find the check byte at the end of the packed blob section."); + + m_packed.swap(packed); + + struct _handler + { + uintptr_t m_max_value; + packed_trie_map& m_parent; + + void root_offset(size_t /*i*/, const uintptr_t& /*v*/) const + {} + + void node_value(size_t i, const uintptr_t& v) const + { + if (v == m_max_value) + m_parent.m_packed[i] = 0; + else + { + // Replace the value index with its memory address. + const value_type& val = m_parent.m_value_store[v]; + const uintptr_t addr = reinterpret_cast(&val); + m_parent.m_packed[i] = addr; + } + } + + void node_index_size(size_t /*i*/, const uintptr_t& /*v*/) const + {} + void node_child_key(size_t /*i*/, const uintptr_t& /*v*/) const + {} + void node_child_offset(size_t /*i*/, const uintptr_t& /*v*/) const + {} + + _handler(packed_trie_map& parent) : m_max_value(0), m_parent(parent) + { + m_max_value = ~m_max_value; + } + + } handler(*this); + + traverse_buffer(handler); +} + +template +void packed_trie_map::dump_structure() const +{ +#ifdef MDDS_TRIE_MAP_DEBUG + dump_packed_trie(); + + cout << "--" << endl; + cout << "entry size: " << m_value_store.size() << endl; + cout << "memory size: " << m_packed.size() << endl; + + const uintptr_t* head = m_packed.data(); + + struct _handler + { + const uintptr_t* m_head; + + _handler(const uintptr_t* head) : m_head(head) + {} + + void node(const uintptr_t* node_pos, key_unit_type c, size_t depth, size_t index_size) + { + uintptr_t value_ptr = *node_pos; + size_t offset = std::distance(m_head, node_pos); + + cout << " --" << endl; + cout << " offset: " << offset << endl; + cout << " key: " << c << endl; + cout << " depth: " << depth << endl; + cout << " child count: " << (index_size / 2) << endl; + cout << " value address: " << value_ptr; + + if (value_ptr) + { + auto p = reinterpret_cast(value_ptr); + cout << "; value: " << *p; + } + + cout << endl; + } + + void move_up(const uintptr_t* node_pos, const uintptr_t* child_pos, const uintptr_t* child_end) + { + size_t offset = std::distance(m_head, node_pos); + size_t child_size = std::distance(child_pos, child_end) / 2; + cout << " --" << endl; + cout << " move up: (offset: " << offset << "; child count: " << child_size << ")" << endl; + } + + void next_child() + { + cout << " next child" << endl; + } + + void end() + {} + + } handler(head); + + traverse_tree(handler); +#endif +} + +template +const uintptr_t* packed_trie_map::find_prefix_node( + const uintptr_t* p, const key_unit_type* prefix, const key_unit_type* prefix_end) const +{ + if (prefix == prefix_end) + return p; + + const uintptr_t* p0 = p; // store the head offset position of this node. + + // Find the child node with a matching key character. + + ++p; + size_t index_size = *p; + size_t n = index_size / 2; + ++p; + + if (!n) + // This is a leaf node. + return nullptr; + + for (size_type low = 0, high = n - 1; low <= high;) + { + size_type i = (low + high) / 2; + + const uintptr_t* p_this = p + i * 2; + key_unit_type node_key = *p_this; + size_t offset = *(p_this + 1); + + if (*prefix == node_key) + { + // Match found! + const uintptr_t* p_child = p0 - offset; + ++prefix; + return find_prefix_node(p_child, prefix, prefix_end); + } + + if (low == high) + // No more child node key to test. Bail out. + break; + + if (high - low == 1) + { + // Only two more child keys left. + if (i == low) + low = high; + else + { + assert(i == high); + high = low; + } + } + else if (*prefix < node_key) + // Move on to the lower sub-group. + high = i; + else + // Move on to the higher sub-group. + low = i; + } + + return nullptr; +} + +template +void packed_trie_map::find_prefix_node_with_stack( + node_stack_type& node_stack, const uintptr_t* p, const key_unit_type* prefix, const key_unit_type* prefix_end) const +{ + if (prefix == prefix_end) + { + size_t index_size = *(p + 1); + const uintptr_t* child_pos = p + 2; + const uintptr_t* child_end = child_pos + index_size; + node_stack.emplace_back(p, child_pos, child_end); + return; + } + + const uintptr_t* p0 = p; // store the head offset position of this node. + + // Find the child node with a matching key character. + + ++p; + size_t index_size = *p; + size_t n = index_size / 2; + ++p; + + if (!n) + { + // This is a leaf node. + node_stack.emplace_back(nullptr, nullptr, nullptr); + return; + } + + const uintptr_t* child_end = p + index_size; + + for (size_type low = 0, high = n - 1; low <= high;) + { + size_type i = (low + high) / 2; + + const uintptr_t* child_pos = p + i * 2; + key_unit_type node_key = *child_pos; + size_t offset = *(child_pos + 1); + + if (*prefix == node_key) + { + // Match found! + node_stack.emplace_back(p0, child_pos, child_end); + const uintptr_t* p_child = p0 - offset; + ++prefix; + find_prefix_node_with_stack(node_stack, p_child, prefix, prefix_end); + return; + } + + if (low == high) + // No more child node key to test. Bail out. + break; + + if (high - low == 1) + { + // Only two more child keys left. + if (i == low) + low = high; + else + { + assert(i == high); + high = low; + } + } + else if (*prefix < node_key) + // Move on to the lower sub-group. + high = i; + else + // Move on to the higher sub-group. + low = i; + } + + node_stack.emplace_back(nullptr, nullptr, nullptr); +} + +template +template +void packed_trie_map::traverse_tree(_Handler hdl) const +{ + node_stack_type node_stack = get_root_stack(); + stack_item* si = &node_stack.back(); + if (si->child_pos == si->child_end) + // empty container + return; + + const uintptr_t* node_pos = si->node_pos; + const uintptr_t* child_pos = si->child_pos; + const uintptr_t* child_end = si->child_end; + const uintptr_t* p = child_pos; + + for (bool in_tree = true; in_tree;) + { + // Descend until the leaf node is reached by following the left-most child nodes. + while (true) + { + auto key = *p; + size_t depth = node_stack.size(); + ++p; + size_t offset = *p; + + // jump down to the child node. + node_pos -= offset; + p = node_pos; + ++p; + size_t index_size = *p; // size of the buffer that stores child nodes' keys and offsets. + + hdl.node(node_pos, key, depth, index_size); + + ++p; + child_pos = p; + child_end = child_pos + index_size; + + // Push this child node onto the stack. + node_stack.emplace_back(node_pos, child_pos, child_end); + + if (!index_size) + // no child nodes i.e. leaf node. Bail out of the loop. + break; + } + + // Ascend up the tree until a node with an unvisited child node is + // found, then move sideways. + while (true) + { + // move up. + node_stack.pop_back(); + si = &node_stack.back(); + hdl.move_up(si->node_pos, si->child_pos, si->child_end); + std::advance(si->child_pos, 2); // move to the next child node slot. + if (si->child_pos != si->child_end) + { + // This is an unvisited child node. Bail out of the loop. + hdl.next_child(); + node_pos = si->node_pos; + p = si->child_pos; + break; + } + + if (node_stack.size() == 1) + { + // End of the tree has reached. + in_tree = false; + break; + } + } + } + + hdl.end(); +} + +template +template +void packed_trie_map::traverse_buffer(_Handler hdl) const +{ + size_t n = m_packed.size(); + size_t i = 0; + hdl.root_offset(i, m_packed[i]); + ++i; + + while (i < n) + { + hdl.node_value(i, m_packed[i]); + ++i; + + hdl.node_index_size(i, m_packed[i]); + size_t index_size = m_packed[i]; + ++i; + index_size /= 2; + + for (size_t j = 0; j < index_size; ++j) + { + hdl.node_child_key(i, m_packed[i]); + ++i; + hdl.node_child_offset(i, m_packed[i]); + ++i; + } + } +} + +} // namespace mdds + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/mdds/trie_map_itr.hpp b/include/mdds/trie_map_itr.hpp new file mode 100644 index 0000000..cc993fb --- /dev/null +++ b/include/mdds/trie_map_itr.hpp @@ -0,0 +1,889 @@ +/************************************************************************* + * + * Copyright (c) 2016-2020 Kohei Yoshida + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************/ + +#ifndef INCLUDED_MDDS_TRIE_MAP_ITR_HPP +#define INCLUDED_MDDS_TRIE_MAP_ITR_HPP + +#include +#include +#include +#ifdef MDDS_TRIE_MAP_DEBUG +#include +#endif + +#include "mdds/global.hpp" +#include "mdds/ref_pair.hpp" + +namespace mdds { namespace trie { namespace detail { + +enum class iterator_type +{ + /** + * Normal iterator is expected to have at least one element on the node + * stack i.e. root. + */ + normal, + /** + * End iterator is the same as a normal iterator except that it is + * positioned past the last node position. A normal iterator becomes an + * end iterator when incrementing past the last node position. + */ + end, + /** + * Empty iterator doesn't reference any node in the tree but still is a + * valid iterator (therefore differs from a singular iterator). Its node + * stack is empty. + */ + empty +}; + +enum empty_iterator_type +{ + empty_iterator +}; + +template +struct get_node_stack_type; + +template +struct get_node_stack_type<_TrieType, std::true_type> +{ + using type = typename _TrieType::const_node_stack_type; +}; + +template +struct get_node_stack_type<_TrieType, std::false_type> +{ + using type = typename _TrieType::node_stack_type; +}; + +template +class search_results; + +template +class iterator_base +{ +protected: + using trie_type = _TrieType; + + using _is_const = std::bool_constant; + + friend trie_type; + friend search_results; + + using node_stack_type = typename get_node_stack_type::type; + using trie_node_type = mdds::detail::const_t; + using trie_node_child_pos_type = typename mdds::detail::get_iterator_type< + typename std::remove_const::type::children_type, _is_const>::type; + + using key_traits_type = typename trie_type::key_traits_type; + using key_type = typename key_traits_type::key_type; + using key_buffer_type = typename key_traits_type::key_buffer_type; + using trie_value_type = typename mdds::detail::const_or_not::type; + +public: + // iterator traits + using value_type = mdds::detail::ref_pair::type, trie_value_type>; + using pointer = value_type*; + using reference = value_type&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::bidirectional_iterator_tag; + +protected: + node_stack_type m_node_stack; + key_buffer_type m_buffer; + key_type m_current_key; + trie_value_type* m_current_value_ptr; + iterator_type m_type; + + static trie_node_type* push_child_node_to_stack( + node_stack_type& node_stack, key_buffer_type& buf, trie_node_child_pos_type& child_pos) + { + using ktt = key_traits_type; + + trie_node_type* node = &child_pos->second; + ktt::push_back(buf, child_pos->first); + node_stack.emplace_back(node, node->children.begin()); + + return node; + } + + /** + * From the current node, move to its previous child node and descend all + * the way to the leaf node. + */ + static trie_node_type* descend_to_previus_leaf_node(node_stack_type& node_stack, key_buffer_type& buf) + { + using ktt = key_traits_type; + + trie_node_type* cur_node = nullptr; + + do + { + // Keep moving down on the right-most child nodes until the + // leaf node is reached. + + auto& si = node_stack.back(); + + --si.child_pos; + ktt::push_back(buf, si.child_pos->first); + cur_node = &si.child_pos->second; + node_stack.emplace_back(cur_node, cur_node->children.end()); + } while (!cur_node->children.empty()); + + return cur_node; + } + + iterator_base(empty_iterator_type) : m_current_value_ptr(nullptr), m_type(iterator_type::empty) + {} + +public: + iterator_base() : m_current_value_ptr(nullptr), m_type(iterator_type::normal) + {} + + iterator_base(node_stack_type&& node_stack, key_buffer_type&& buf, iterator_type type) + : m_node_stack(std::move(node_stack)), m_buffer(std::move(buf)), + m_current_key(key_traits_type::to_key(m_buffer)), m_current_value_ptr(&m_node_stack.back().node->value), + m_type(type) + {} + + bool operator==(const iterator_base& other) const + { + if (m_type != other.m_type) + return false; + + if (m_type == iterator_type::empty) + return true; + + return m_node_stack.back() == other.m_node_stack.back(); + } + + bool operator!=(const iterator_base& other) const + { + return !operator==(other); + } + + value_type operator*() + { + return value_type(m_current_key, *m_current_value_ptr); + } + + value_type operator->() + { + return value_type(m_current_key, *m_current_value_ptr); + } + + iterator_base& operator++() + { + using ktt = key_traits_type; + + trie_node_type* cur_node = m_node_stack.back().node; + + do + { + if (cur_node->children.empty()) + { + // Current node is a leaf node. Keep moving up the stack until we + // reach a parent node with unvisited children. + + while (true) + { + if (m_node_stack.size() == 1) + { +#ifdef MDDS_TRIE_MAP_DEBUG + if (m_type == iterator_type::end) + { + std::ostringstream os; + os << "iterator_base::operator++#" << __LINE__ << ": moving past the end position!"; + throw general_error(os.str()); + } +#endif + // We've reached the end position. Bail out. + m_type = iterator_type::end; + return *this; + } + + // Move up one parent and see if it has an unvisited child node. + ktt::pop_back(m_buffer); + m_node_stack.pop_back(); + auto& si = m_node_stack.back(); + ++si.child_pos; + + if (si.child_pos != si.node->children.end()) + { + // Move down to this unvisited child node. + cur_node = push_child_node_to_stack(m_node_stack, m_buffer, si.child_pos); + break; + } + } + } + else + { + // Current node has child nodes. Follow the first child node. + auto child_pos = cur_node->children.begin(); + cur_node = push_child_node_to_stack(m_node_stack, m_buffer, child_pos); + } + } while (!cur_node->has_value); + + m_current_key = ktt::to_key(m_buffer); + m_current_value_ptr = &cur_node->value; + return *this; + } + + iterator_base operator++(int) + { + iterator_base tmp(*this); + operator++(); + return tmp; + } + + iterator_base& operator--() + { + using ktt = key_traits_type; + trie_node_type* cur_node = m_node_stack.back().node; + + if (m_type == iterator_type::end && cur_node->has_value) + { + assert(m_node_stack.size() == 1); + m_type = iterator_type::normal; + } + else if (m_node_stack.size() == 1) + { + // This is the end position aka root node. Move down to the + // right-most leaf node. + auto& si = m_node_stack.back(); + assert(si.child_pos == cur_node->children.end()); + cur_node = descend_to_previus_leaf_node(m_node_stack, m_buffer); + m_type = iterator_type::normal; + } + else if (cur_node->children.empty()) + { + // This is a leaf node. Keep going up until it finds a parent + // node with unvisited child nodes on the left side, then descend + // on that path all the way to its leaf. + + do + { + // Go up one node. + + ktt::pop_back(m_buffer); + m_node_stack.pop_back(); + auto& si = m_node_stack.back(); + cur_node = si.node; + + if (si.child_pos != cur_node->children.begin()) + { + // Left and down. + cur_node = descend_to_previus_leaf_node(m_node_stack, m_buffer); + assert(cur_node->has_value); + } + } while (!cur_node->has_value); + } + else + { + // Non-leaf node with value. Keep going up until either the root + // node or another node with value is reached. + + assert(cur_node->has_value); + assert(m_node_stack.back().child_pos == cur_node->children.begin()); + + do + { + // Go up. + ktt::pop_back(m_buffer); + m_node_stack.pop_back(); + auto& si = m_node_stack.back(); + cur_node = si.node; + + if (m_node_stack.size() == 1) + { + // Root node reached. Left and down. + cur_node = descend_to_previus_leaf_node(m_node_stack, m_buffer); + assert(cur_node->has_value); + } + } while (!cur_node->has_value); + } + + assert(cur_node->has_value); + m_current_key = ktt::to_key(m_buffer); + m_current_value_ptr = &cur_node->value; + return *this; + } + + iterator_base operator--(int) + { + iterator_base tmp(*this); + operator--(); + return tmp; + } +}; + +template +class const_iterator; + +template +class iterator : public iterator_base<_TrieType, false> +{ + using trie_type = _TrieType; + + friend trie_type; + friend search_results; + friend const_iterator; + + using base_type = iterator_base; + using node_stack_type = typename base_type::node_stack_type; + using key_buffer_type = typename base_type::key_buffer_type; + + iterator(empty_iterator_type t) : base_type(t) + {} + +public: + iterator() : base_type() + {} + + iterator(node_stack_type&& node_stack, key_buffer_type&& buf, iterator_type type) + : base_type(std::move(node_stack), std::move(buf), type) + {} +}; + +template +class const_iterator : public iterator_base<_TrieType, true> +{ + using trie_type = _TrieType; + + friend trie_type; + friend search_results; + + using base_type = iterator_base; + using node_stack_type = typename base_type::node_stack_type; + using key_buffer_type = typename base_type::key_buffer_type; + + using base_type::m_buffer; + using base_type::m_current_key; + using base_type::m_current_value_ptr; + using base_type::m_node_stack; + using base_type::m_type; + + const_iterator(empty_iterator_type t) : base_type(t) + {} + +public: + const_iterator() : base_type() + {} + + const_iterator(node_stack_type&& node_stack, key_buffer_type&& buf, iterator_type type) + : base_type(std::move(node_stack), std::move(buf), type) + {} + + const_iterator(const iterator<_TrieType>& it) : base_type() + { + m_buffer = it.m_buffer; + m_current_key = it.m_current_key; + m_current_value_ptr = it.m_current_value_ptr; + m_type = it.m_type; + + for (const auto& e : it.m_node_stack) + m_node_stack.emplace_back(e.node, e.child_pos); + } +}; + +template +bool operator==(const iterator<_TrieType>& left, const const_iterator<_TrieType>& right) +{ + return const_iterator<_TrieType>(left) == right; +} + +template +bool operator!=(const iterator<_TrieType>& left, const const_iterator<_TrieType>& right) +{ + return const_iterator<_TrieType>(left) != right; +} + +template +bool operator==(const const_iterator<_TrieType>& left, const iterator<_TrieType>& right) +{ + return left == const_iterator<_TrieType>(right); +} + +template +bool operator!=(const const_iterator<_TrieType>& left, const iterator<_TrieType>& right) +{ + return left != const_iterator<_TrieType>(right); +} + +template +class search_results +{ + using trie_type = _TrieType; + friend trie_type; + using node_stack_type = typename trie_type::const_node_stack_type; + + using trie_node = typename trie_type::trie_node; + using key_traits_type = typename trie_type::key_traits_type; + using key_type = typename key_traits_type::key_type; + using key_buffer_type = typename key_traits_type::key_buffer_type; + using key_unit_type = typename key_traits_type::key_unit_type; + + const trie_node* m_node; + key_buffer_type m_buffer; + node_stack_type m_node_stack; + + search_results(const trie_node* node, key_buffer_type&& buf) : m_node(node), m_buffer(buf) + {} + +public: + using const_iterator = typename trie_type::const_iterator; + + const_iterator begin() const + { + if (!m_node) + // empty results. + return const_iterator(empty_iterator); + + // Push the root node. + key_buffer_type buf(m_buffer); + node_stack_type node_stack; + node_stack.emplace_back(m_node, m_node->children.begin()); + + while (!node_stack.back().node->has_value) + { + // There should always be at least one value node along the + // left-most branch. + + auto it = node_stack.back().child_pos; + const_iterator::push_child_node_to_stack(node_stack, buf, it); + } + + return const_iterator(std::move(node_stack), std::move(buf), iterator_type::normal); + } + + const_iterator end() const + { + if (!m_node) + // empty results. + return const_iterator(empty_iterator); + + node_stack_type node_stack; + node_stack.emplace_back(m_node, m_node->children.end()); + return const_iterator(std::move(node_stack), key_buffer_type(m_buffer), iterator_type::end); + } +}; + +template +class packed_search_results; + +template +class packed_iterator_base +{ + using trie_type = _TrieType; + friend trie_type; + friend packed_search_results; + + using stack_item = typename trie_type::stack_item; + using node_stack_type = typename trie_type::node_stack_type; + + using key_traits_type = typename trie_type::key_traits_type; + using key_type = typename key_traits_type::key_type; + using key_buffer_type = typename key_traits_type::key_buffer_type; + using key_unit_type = typename key_traits_type::key_unit_type; + +public: + // iterator traits + using value_type = typename trie_type::key_value_type; + using pointer = value_type*; + using reference = value_type&; + using difference_type = std::ptrdiff_t; + using iterator_category = std::bidirectional_iterator_tag; + +private: + node_stack_type m_node_stack; + key_buffer_type m_buffer; + value_type m_current_value; + iterator_type m_type; + + /** + * Given a child offset position (child_pos), jump to the actual child + * node position and push that onto the stack as stack_item. + */ + static void push_child_node_to_stack(node_stack_type& node_stack, key_buffer_type& buf, const uintptr_t* child_pos) + { + using ktt = key_traits_type; + + const uintptr_t* node_pos = node_stack.back().node_pos; + + key_unit_type c = static_cast(*child_pos); + ktt::push_back(buf, c); + ++child_pos; + size_t offset = static_cast(*child_pos); + node_pos -= offset; // Jump to the head of the child node. + const uintptr_t* p = node_pos; + ++p; + size_t index_size = *p; + ++p; + child_pos = p; + const uintptr_t* child_end = child_pos + index_size; + + // Push it onto the stack. + node_stack.emplace_back(node_pos, child_pos, child_end); + } + + static const void descend_to_previus_leaf_node(node_stack_type& node_stack, key_buffer_type& buf) + { + using ktt = key_traits_type; + + const uintptr_t* node_pos = nullptr; + size_t index_size = 0; + + do + { + // Keep moving down on the right-most child nodes until the + // leaf node is reached. + + stack_item* si = &node_stack.back(); + node_pos = si->node_pos; + --si->child_pos; + size_t offset = *si->child_pos; + --si->child_pos; + key_unit_type c = *si->child_pos; + node_pos -= offset; // Jump to the head of the child node. + ktt::push_back(buf, c); + + const uintptr_t* p = node_pos; + ++p; + index_size = *p; + ++p; + const uintptr_t* child_pos = p; + const uintptr_t* child_end = child_pos + index_size; + node_stack.emplace_back(node_pos, child_end, child_end); + } while (index_size); + } + + packed_iterator_base(empty_iterator_type) : m_type(iterator_type::empty) + {} + +public: + packed_iterator_base() : m_type(iterator_type::normal) + {} + + packed_iterator_base(node_stack_type&& node_stack, key_buffer_type&& buf, const typename trie_type::value_type& v) + : m_node_stack(std::move(node_stack)), m_buffer(std::move(buf)), + m_current_value(key_traits_type::to_key(m_buffer), v), m_type(iterator_type::normal) + {} + + packed_iterator_base(node_stack_type&& node_stack, key_buffer_type&& buf) + : m_node_stack(std::move(node_stack)), m_buffer(std::move(buf)), m_type(iterator_type::end) + {} + + bool operator==(const packed_iterator_base& other) const + { + if (m_type != other.m_type) + return false; + + if (m_type == iterator_type::empty) + return true; + + return m_node_stack.back() == other.m_node_stack.back(); + } + + bool operator!=(const packed_iterator_base& other) const + { + return !operator==(other); + } + + const value_type& operator*() + { + return m_current_value; + } + + const value_type* operator->() + { + return &m_current_value; + } + + packed_iterator_base& operator++() + { + using ktt = key_traits_type; + + stack_item* si = &m_node_stack.back(); + const typename trie_type::value_type* pv = nullptr; + size_t index_size = *(si->node_pos + 1); + + do + { + if (!index_size) + { + // Current node is a leaf node. Keep moving up the stack until we + // reach a parent node with unvisited children. + + while (true) + { + if (m_node_stack.size() == 1) + { +#ifdef MDDS_TRIE_MAP_DEBUG + if (m_type == iterator_type::end) + { + std::ostringstream os; + os << "packed_iterator_base::operator++#" << __LINE__ << ": moving past the end position!"; + throw general_error(os.str()); + } +#endif + // We've reached the end position. Bail out. + m_type = iterator_type::end; + return *this; + } + + // Move up one parent and see if it has an unvisited child node. + ktt::pop_back(m_buffer); + m_node_stack.pop_back(); + si = &m_node_stack.back(); + std::advance(si->child_pos, 2); + + if (si->child_pos != si->child_end) + { + // Move down to this unvisited child node. + push_child_node_to_stack(m_node_stack, m_buffer, si->child_pos); + break; + } + } + } + else + { + // Current node has child nodes. Follow the first child node. + push_child_node_to_stack(m_node_stack, m_buffer, si->child_pos); + } + + si = &m_node_stack.back(); + pv = reinterpret_cast(*si->node_pos); + index_size = *(si->node_pos + 1); + } while (!pv); + + assert(pv); + m_current_value = value_type(ktt::to_key(m_buffer), *pv); + + return *this; + } + + packed_iterator_base operator++(int) + { + packed_iterator_base tmp(*this); + operator++(); + return tmp; + } + + packed_iterator_base& operator--() + { + using ktt = key_traits_type; + + stack_item* si = &m_node_stack.back(); + const typename trie_type::value_type* pv = + reinterpret_cast(*si->node_pos); + size_t index_size = *(si->node_pos + 1); // index size for child nodes. + + if (m_type == iterator_type::end && pv) + { + assert(m_node_stack.size() == 1); + m_type = iterator_type::normal; + } + else if (m_node_stack.size() == 1) + { + // This is the end position aka root node. Move down to the + // right-most leaf node. + assert(si->child_pos == si->child_end); + descend_to_previus_leaf_node(m_node_stack, m_buffer); + si = &m_node_stack.back(); + pv = reinterpret_cast(*si->node_pos); + m_type = iterator_type::normal; + } + else if (!index_size) + { + // This is a leaf node. Keep going up until it finds a parent + // node with unvisited child nodes on the left side, then descend + // on that path all the way to its leaf. + + do + { + // Go up one node. + + ktt::pop_back(m_buffer); + m_node_stack.pop_back(); + si = &m_node_stack.back(); + const uintptr_t* p = si->node_pos; + pv = reinterpret_cast(*p); + ++p; + index_size = *p; + ++p; + const uintptr_t* first_child = p; + + if (si->child_pos != first_child) + { + // Left and down. + descend_to_previus_leaf_node(m_node_stack, m_buffer); + si = &m_node_stack.back(); + p = si->node_pos; + pv = reinterpret_cast(*p); + assert(pv); + } + } while (!pv); + } + else + { + // Non-leaf node with value. Keep going up until either the root + // node or another node with value is reached. + + assert(*si->node_pos); // this node should have a value. + assert(si->child_pos == (si->node_pos + 2)); + + do + { + // Go up. + ktt::pop_back(m_buffer); + m_node_stack.pop_back(); + si = &m_node_stack.back(); + pv = reinterpret_cast(*si->node_pos); + + if (m_node_stack.size() == 1) + { + // Root node reached. + descend_to_previus_leaf_node(m_node_stack, m_buffer); + si = &m_node_stack.back(); + pv = reinterpret_cast(*si->node_pos); + assert(pv); + } + } while (!pv); + } + + assert(pv); + m_current_value = value_type(ktt::to_key(m_buffer), *pv); + + return *this; + } + + packed_iterator_base operator--(int) + { + packed_iterator_base tmp(*this); + operator--(); + return tmp; + } +}; + +template +class packed_search_results +{ + using trie_type = _TrieType; + friend trie_type; + using node_stack_type = typename trie_type::node_stack_type; + + using key_traits_type = typename trie_type::key_traits_type; + using key_type = typename key_traits_type::key_type; + using key_buffer_type = typename key_traits_type::key_buffer_type; + using key_unit_type = typename key_traits_type::key_unit_type; + + const uintptr_t* m_node; + key_buffer_type m_buffer; + + packed_search_results(const uintptr_t* node, key_buffer_type&& buf) : m_node(node), m_buffer(std::move(buf)) + {} + + node_stack_type get_root_node() const + { + const uintptr_t* p = m_node; + ++p; + size_t index_size = *p; + ++p; + const uintptr_t* child_pos = p; + const uintptr_t* child_end = child_pos + index_size; + + // Push this child node onto the stack. + node_stack_type node_stack; + node_stack.emplace_back(m_node, child_pos, child_end); + return node_stack; + } + + void swap(packed_search_results& other) + { + std::swap(m_node, other.m_node); + std::swap(m_buffer, other.m_buffer); + } + +public: + using const_iterator = packed_iterator_base; + + packed_search_results() : m_node(nullptr) + {} + + packed_search_results(const packed_search_results& other) : m_node(other.m_node), m_buffer(other.m_buffer) + {} + + packed_search_results(packed_search_results&& other) : m_node(other.m_node), m_buffer(std::move(other.m_buffer)) + { + other.m_node = nullptr; + } + + packed_search_results& operator=(packed_search_results other) + { + packed_search_results tmp(std::move(other)); + swap(tmp); + return *this; + } + + const_iterator begin() const + { + if (!m_node) + // empty results. + return const_iterator(empty_iterator); + + // Push the root node. + key_buffer_type buf(m_buffer); + node_stack_type node_stack = get_root_node(); + + while (!node_stack.back().has_value()) + { + // There should always be at least one value node along the + // left-most branch. + + const_iterator::push_child_node_to_stack(node_stack, buf, node_stack.back().child_pos); + } + + return const_iterator(std::move(node_stack), std::move(buf), *node_stack.back().get_value()); + } + + const_iterator end() const + { + if (!m_node) + // empty results. + return const_iterator(empty_iterator); + + node_stack_type node_stack = get_root_node(); + auto& si = node_stack.back(); + si.child_pos = si.child_end; + return const_iterator(std::move(node_stack), key_buffer_type(m_buffer)); + } +}; + +}}} // namespace mdds::trie::detail + +#endif -- cgit v1.2.3