Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| SHA1 Hash: | 64680991bdc285b469ff012f6bebfcc498170455 |
|---|---|
| Date: | 2008-11-21 14:19:00 |
| User: | stephan |
| Comment: | egg |
Tags And Properties
- branch=trunk inherited from [2bd6172b61]
- sym-trunk inherited from [2bd6172b61]
Changes
Added Makefile
| Old () | New (6c7cd75103ca0205) | |||
|---|---|---|---|---|
| > | 1 | #!/usr/bin/make -f | ||
| > | 2 | # Requires GNU Make 3.80+! | ||
| > | 3 | default: all | ||
| > | 4 | |||
| > | 5 | ifeq (1,$(TCC)) | ||
| > | 6 | # If you have (want to use) tcc, try this: | ||
| > | 7 | CC := tcc | ||
| > | 8 | CXX := tcc | ||
| > | 9 | CPPFLAGS += -gb -bt 10 \ | ||
| > | 10 | -Wwrite-strings \ | ||
| > | 11 | -Wunsupported \ | ||
| > | 12 | -Wall | ||
| > | 13 | else | ||
| > | 14 | # Assume gcc-compatible flags: | ||
| > | 15 | CXXFLAGS += -g | ||
| > | 16 | # CXXFLAGS += -Os | ||
| > | 17 | CFLAGS += \ | ||
| > | 18 | -Wimplicit-function-declaration \ | ||
| > | 19 | -Wall \ | ||
| > | 20 | -g | ||
| > | 21 | endif | ||
| > | 22 | |||
| > | 23 | # CPPFLAGS ?= -g | ||
| > | 24 | # -Wall -Werror | ||
| > | 25 | |||
| > | 26 | PACKAGE.NAME = libwhgc | ||
| > | 27 | PACKAGE.VERSION := $(shell date +%Y%m%d) | ||
| > | 28 | ShakeNMake.DOXYGEN.GENERATE_LATEX := NO | ||
| > | 29 | ShakeNMake.DOXYGEN.USE_DOT := 0 | ||
| > | 30 | include shake-n-make.make | ||
| > | 31 | |||
| > | 32 | #CFLAGS += -std=c99 | ||
| > | 33 | #CFLAGS += -xc99=all # SUN CC | ||
| > | 34 | |||
| > | 35 | INCLUDES += -I. | ||
| > | 36 | |||
| > | 37 | ######################################################################## | ||
| > | 38 | # Hashtable lib: | ||
| > | 39 | HASH_OBJ := $(patsubst %,%.o,whhash) | ||
| > | 40 | libwhhash.LIB.OBJECTS = $(HASH_OBJ) | ||
| > | 41 | $(call ShakeNMake.CALL.RULES.LIBS,libwhhash) | ||
| > | 42 | |||
| > | 43 | ######################################################################## | ||
| > | 44 | # GC lib: | ||
| > | 45 | WHGC_OBJ := whgc.o whrc.o | ||
| > | 46 | libwhgc.LIB.OBJECTS = $(WHGC_OBJ) $(HASH_OBJ) | ||
| > | 47 | $(call ShakeNMake.CALL.RULES.LIBS,libwhgc) | ||
| > | 48 | $(libwhgc.LIB): $(libwhhash.LIB) | ||
| > | 49 | libs: $(libwhgc.LIB) | ||
| > | 50 | |||
| > | 51 | # test.BIN.LDFLAGS := $(libwhgc.LIB) | ||
| > | 52 | test.BIN.OBJECTS := test.o $(libwhgc.LIB) | ||
| > | 53 | $(call ShakeNMake.CALL.RULES.BINS,test) | ||
| > | 54 | $(test.BIN): $(libpegc.LIB) | ||
| > | 55 | bins: $(test.BIN) | ||
| > | 56 | |||
| > | 57 | CLEAN_FILES += *~ *.valgrind *.a | ||
| > | 58 | |||
| > | 59 | PACKAGE.DIST_FILES += $(wildcard *.c *.h) | ||
| > | 60 | |||
| > | 61 | all: libs bins | ||
Added grindit.sh
| Old () | New (e8f473630fa05dc4) | |||
|---|---|---|---|---|
| > | 1 | #!/bin/bash | ||
| > | 2 | if [[ x != "x$1" ]]; then | ||
| > | 3 | app=./$1 | ||
| > | 4 | shift | ||
| > | 5 | else | ||
| > | 6 | app=./test | ||
| > | 7 | fi | ||
| > | 8 | make ${app} || rm -f ${app} | ||
| > | 9 | test -x $app || { | ||
| > | 10 | echo "${app} not executable." | ||
| > | 11 | exit 3 | ||
| > | 12 | } | ||
| > | 13 | |||
| > | 14 | log=${app}.valgrind | ||
| > | 15 | valgrind --log-fd=3 \ | ||
| > | 16 | --leak-check=full -v \ | ||
| > | 17 | --show-reachable=yes \ | ||
| > | 18 | ${app} "$@" 3>${log} | ||
| > | 19 | x=$? | ||
| > | 20 | grep malloc/free ${log} && { | ||
| > | 21 | echo "Details are in [${log}]" | ||
| > | 22 | } | ||
| > | 23 | exit $x | ||
Added shake-n-make.make
| Old () | New (81dbd3be3a8a77ea) | |||
|---|---|---|---|---|
| > | 1 | #!/usr/bin/make -f | ||
| > | 2 | all: | ||
| > | 3 | SHELL=/bin/bash | ||
| > | 4 | MAKE_REQUIRED_VERSION := 380# MAKE_VERSION stripped of any dots | ||
| > | 5 | VERSION_CHECK := \ | ||
| > | 6 | $(shell \ | ||
| > | 7 | test $$(echo $(MAKE_VERSION) | sed -e 's/\.//g') -ge \ | ||
| > | 8 | "$(MAKE_REQUIRED_VERSION)" 2>/dev/null \ | ||
| > | 9 | && echo 1 || echo 0) | ||
| > | 10 | |||
| > | 11 | ifneq (1,$(VERSION_CHECK)) | ||
| > | 12 | $(error Your version of Make ($(MAKE_VERSION)) is too old to use this code!) | ||
| > | 13 | endif | ||
| > | 14 | |||
| > | 15 | |||
| > | 16 | ######################################################################## | ||
| > | 17 | # This file defines a set of basic targets for single-dir C++ projects | ||
| > | 18 | # trees, designed for GNU make and gcc. The code in this file is | ||
| > | 19 | # intended to remain project-neutral. Add your project-specific stuff | ||
| > | 20 | # in higher-level makefiles and include this one from there. | ||
| > | 21 | # | ||
| > | 22 | # The shell code in this makefile assumes GNU Make, GNU Bash and GNU | ||
| > | 23 | # versions several other common system tools, like mkdir, tar, sed, | ||
| > | 24 | # etc. | ||
| > | 25 | # | ||
| > | 26 | ######################################################################## | ||
| > | 27 | # Distribution policy: | ||
| > | 28 | # | ||
| > | 29 | # Public Domain, of course. No warranties at all: if it destroys your | ||
| > | 30 | # data or your marriage, it's your faul (or at least it's not MY fault). | ||
| > | 31 | # | ||
| > | 32 | # i ACTUALLY DO use this code in several source trees, so please send | ||
| > | 33 | # in improvements: http://wanderinghorse.net/computing/make/ | ||
| > | 34 | # | ||
| > | 35 | ######################################################################## | ||
| > | 36 | # Main features: | ||
| > | 37 | # | ||
| > | 38 | # - Rules for compiling C/C++ sources. | ||
| > | 39 | # | ||
| > | 40 | # - Rules for building binaries from C/C++ sources. | ||
| > | 41 | # | ||
| > | 42 | # - Rules for building shared/static libraries and binaries. | ||
| > | 43 | # | ||
| > | 44 | # - Rules for building a tarred or zipped distribution file. | ||
| > | 45 | # | ||
| > | 46 | # - Rules for installation of arbitrary file sets. | ||
| > | 47 | # | ||
| > | 48 | # - Transparent and automatic (and *fast*) dependencies generation for | ||
| > | 49 | # C/C++ files. | ||
| > | 50 | # | ||
| > | 51 | ######################################################################## | ||
| > | 52 | # Ultra-quick usage overview: | ||
| > | 53 | # | ||
| > | 54 | # Include it from your Makefile like so: | ||
| > | 55 | # | ||
| > | 56 | # PACKAGE.NAME = single-token name of your project (e.g., libfoo) | ||
| > | 57 | # PACKAGE.VERSION = project version number, in an arbitrary format. | ||
| > | 58 | # include shake-n-make.mk | ||
| > | 59 | # | ||
| > | 60 | # Using spaces or special shell characters in .NAME and .VERSION may | ||
| > | 61 | # cause problems in this code. | ||
| > | 62 | # | ||
| > | 63 | # PACKAGE.* are used when building a distribution tarball. | ||
| > | 64 | # | ||
| > | 65 | # You can optionally define the following variables: | ||
| > | 66 | # CLEAN_FILES += list of files/dirs to delete during 'make clean' | ||
| > | 67 | # DISTCLEAN_FILES += list of files/dirs to delete during 'make distclean' | ||
| > | 68 | # PACKAGE.DIST_FILES += list of files/dirs to include in distribution | ||
| > | 69 | # tarball/zip file. | ||
| > | 70 | # | ||
| > | 71 | # | ||
| > | 72 | # Achtung: | ||
| > | 73 | # ***** Note the use of "+=" on several of those variables!!!! ***** | ||
| > | 74 | # ***** BE CAREFUL WHEN USING [DIST]CLEAN_FILES!!!! ***** | ||
| > | 75 | # | ||
| > | 76 | # The rules for bins, shared libs, etc., have to be installed by the | ||
| > | 77 | # user, but it's really simple. The subsections below explain how to | ||
| > | 78 | # use the various features. | ||
| > | 79 | # | ||
| > | 80 | # To install your package do: | ||
| > | 81 | # | ||
| > | 82 | # make install prefix=/install/prefix | ||
| > | 83 | # | ||
| > | 84 | # where $(prefix) normally defaults to /usr/local. (Setting prefix | ||
| > | 85 | # to $HOME is often useful.) | ||
| > | 86 | # | ||
| > | 87 | ######################################################################## | ||
| > | 88 | # Compiling .o files from *.cpp and *.c: | ||
| > | 89 | # | ||
| > | 90 | # This file provides flexible rules for building these. See the %.o | ||
| > | 91 | # targets for all of the accepted flags. | ||
| > | 92 | # | ||
| > | 93 | ######################################################################## | ||
| > | 94 | # Building a binary executable: | ||
| > | 95 | # | ||
| > | 96 | # mybin.BIN.OBJECTS = foo.o bar.o | ||
| > | 97 | # mybin.BIN.LDFLAGS = -ldl -lstdc++ | ||
| > | 98 | # $(call ShakeNMake.CALL.RULES.BINS,mybin) | ||
| > | 99 | # | ||
| > | 100 | # You may pass an arbitrary number of binary names to CALL.RULES.BINS. | ||
| > | 101 | # See ShakeNMake.EVAL.RULES.BINS for the full list of configuration | ||
| > | 102 | # vars it accepts. | ||
| > | 103 | # | ||
| > | 104 | # A target named mybin.BIN is created for building the binary and | ||
| > | 105 | # $(mybin.BIN) holds the name of the compiled binary (typically | ||
| > | 106 | # "mybin"). | ||
| > | 107 | # | ||
| > | 108 | ######################################################################## | ||
| > | 109 | # Building a shared library: | ||
| > | 110 | # | ||
| > | 111 | # myDLL.DLL.OBJECTS = foo.o bar.o | ||
| > | 112 | # myDLL.LDFLAGS = -L/a/lib/path -lmylib | ||
| > | 113 | # $(call ShakeNMake.CALL.RULES.DLLS,myDLL) | ||
| > | 114 | # | ||
| > | 115 | # Like CALL.RULES.BINS, you may pass an arbitrary number of DLL names to | ||
| > | 116 | # CALL.RULES.DLLS. See EVAL.RULES.DLLS for the full list of configuration | ||
| > | 117 | # vars it accepts. | ||
| > | 118 | # | ||
| > | 119 | # A target named myDLL.DLL is created for building the library and | ||
| > | 120 | # $(myDLL.DLL) holds the name of the compiled DLL (typically | ||
| > | 121 | # "myDLL.so"). | ||
| > | 122 | # | ||
| > | 123 | ######################################################################## | ||
| > | 124 | # Building a static library is trival: | ||
| > | 125 | # | ||
| > | 126 | # myLib.LIB.OBJECTS = foo.o bar.o | ||
| > | 127 | # $(call ShakeNMake.CALL.RULES.LIBS,myLib) | ||
| > | 128 | # | ||
| > | 129 | # A target named myLib.LIB is created for building the library and | ||
| > | 130 | # $(myLib.LIB) holds the name of the compiled lib (typically | ||
| > | 131 | # "myLib.a"). | ||
| > | 132 | # | ||
| > | 133 | ######################################################################## | ||
| > | 134 | # Installing files: | ||
| > | 135 | # | ||
| > | 136 | # To install files you have to define "install sets", where a "set" is | ||
| > | 137 | # simply a collection of files which all have the same destination. | ||
| > | 138 | # An example: | ||
| > | 139 | # | ||
| > | 140 | # INSTALL.STUFF = $(myLib.LIB) $(myDLL.DLL) $(mybin.BIN) | ||
| > | 141 | # INSTALL.STUFF.DEST = $(prefix)/$(PACKAGE.NAME) | ||
| > | 142 | # $(call ShakeNMake.CALL.RULES.INSTALL,STUFF) | ||
| > | 143 | # | ||
| > | 144 | # That creates install rules named install-STUFF and uninstall-STUFF, | ||
| > | 145 | # which are called by the install and uninstall targets, respectively. | ||
| > | 146 | # | ||
| > | 147 | # You may pass an arbitrary number of install set names to | ||
| > | 148 | # CALL.RULES.INSTALL. | ||
| > | 149 | # | ||
| > | 150 | # Install notes: | ||
| > | 151 | # | ||
| > | 152 | # - There is nothing magical about the word STUFF: any single token can | ||
| > | 153 | # be used. If INSTALL.xxx.DEST is not set then [un]install-xxx will | ||
| > | 154 | # cause an error. | ||
| > | 155 | # | ||
| > | 156 | # - The installation rules are quite braindead, and simply use cp to | ||
| > | 157 | # install files. | ||
| > | 158 | # | ||
| > | 159 | ######################################################################## | ||
| > | 160 | # Distribution tarball: | ||
| > | 161 | # | ||
| > | 162 | # To build a dist tarball: | ||
| > | 163 | # | ||
| > | 164 | # PACKAGE.DIST_FILES += list of files | ||
| > | 165 | # | ||
| > | 166 | # Then: | ||
| > | 167 | # make dist | ||
| > | 168 | # | ||
| > | 169 | # That builds a file named $(PACKAGE.NAME)-$(PACKAGE.VERSION).tar.gz and, | ||
| > | 170 | # if 'zip' is found in your $(PATH) then a zip file is created as well. | ||
| > | 171 | # | ||
| > | 172 | # Note that GNUmakefile and shake-n-make.make are added to DIST_FILES by | ||
| > | 173 | # default, so you don't need to add those. If shake-n-make.make is using | ||
| > | 174 | # mkdep.c to generate C/C++ dependencies than that file is also | ||
| > | 175 | # automatically included in the distribution. | ||
| > | 176 | # | ||
| > | 177 | # If you want to use a non-GNU tar or change the compression type | ||
| > | 178 | # (defaults to gzip) then change the ShakeNMake.TARBALL.FLAGS variable | ||
| > | 179 | # in this file (not in your Makefile). | ||
| > | 180 | # The ShakeNMake.TARBALL.XFLAGS is a special case var which has flags | ||
| > | 181 | # for tar which go AFTER the target filename. e.g. you can set it to | ||
| > | 182 | # --exclude=MySubDir to exclude files from a certain dir. | ||
| > | 183 | # | ||
| > | 184 | ######################################################################## | ||
| > | 185 | # Eye candy: | ||
| > | 186 | # | ||
| > | 187 | # If you want the compiler/linker output to be less verbose, try: | ||
| > | 188 | # | ||
| > | 189 | # ShakeNMake.QUIET = 1 | ||
| > | 190 | # | ||
| > | 191 | # BEFORE including this file. | ||
| > | 192 | # | ||
| > | 193 | ######################################################################## | ||
| > | 194 | # Maintainer's/hacker's notes: | ||
| > | 195 | # | ||
| > | 196 | # Vars names starting with ShakeNMake are mostly internal to this | ||
| > | 197 | # makefile and are considered "private" unless documented otherwise. | ||
| > | 198 | # Notable exceptions are most of the ShakeNMake.CALL entries, which | ||
| > | 199 | # are $(call)able functions, and ShakeNMake.EVAL entries, which are | ||
| > | 200 | # $(eval)able code. | ||
| > | 201 | # | ||
| > | 202 | ######################################################################## | ||
| > | 203 | |||
| > | 204 | ifneq (,$(COMSPEC)) | ||
| > | 205 | $(warning Setting ShakeNMake.SMELLS.LIKE.WINDOWS to 1) | ||
| > | 206 | ShakeNMake.SMELLS.LIKE.WINDOWS := 1 | ||
| > | 207 | ShakeNMake.EXTENSIONS.DLL = .DLL# maintenance reminder: this must stay upper-case! | ||
| > | 208 | ShakeNMake.EXTENSIONS.EXE = .EXE# maintenance reminder: this must stay upper-case! | ||
| > | 209 | else | ||
| > | 210 | ShakeNMake.SMELLS.LIKE.WINDOWS := 0 | ||
| > | 211 | ShakeNMake.EXTENSIONS.DLL = .so | ||
| > | 212 | ShakeNMake.EXTENSIONS.EXE =# no whitespace, please | ||
| > | 213 | endif | ||
| > | 214 | |||
| > | 215 | |||
| > | 216 | ShakeNMake.MAKEFILE = shake-n-make.make | ||
| > | 217 | $(ShakeNMake.MAKEFILE):# avoid breaking some deps checks if someone renames this file (been there, done that) | ||
| > | 218 | |||
| > | 219 | ######################################################################## | ||
| > | 220 | # Core information: | ||
| > | 221 | |||
| > | 222 | ifeq (,$(PACKAGE.NAME)) | ||
| > | 223 | $(error You must set PACKAGE.NAME to a single-token name for your prject, e.g., libMyStuff) | ||
| > | 224 | endif | ||
| > | 225 | |||
| > | 226 | ifeq (,$(PACKAGE.VERSION)) | ||
| > | 227 | $(error You must set PACKAGE.VERSION to a version number for your project, e.g., 1.3.5.7-beta9 or 1.0 or 2007-02-14) | ||
| > | 228 | endif | ||
| > | 229 | |||
| > | 230 | |||
| > | 231 | ######################################################################## | ||
| > | 232 | # auto-add the makefiles to DIST_FILES, filtering out any which start | ||
| > | 233 | # with a dot because we use such files for temp/volitile files which | ||
| > | 234 | # contain Make rules (C/C++ deps, for example). | ||
| > | 235 | PACKAGE.MAKEFILE = $(firstword $(MAKEFILE_LIST))# normally either Makefile or GNUmakefile | ||
| > | 236 | $(PACKAGE.MAKEFILE): | ||
| > | 237 | PACKAGE.DIST_FILES += $(filter-out .%,$(MAKEFILE_LIST)) | ||
| > | 238 | |||
| > | 239 | |||
| > | 240 | ShakeNMake.FORCE: ; @true | ||
| > | 241 | |||
| > | 242 | ######################################################################## | ||
| > | 243 | # DESTDIR is for GNU Autotools compatibility... | ||
| > | 244 | DESTDIR ?= | ||
| > | 245 | prefix ?= /usr/local | ||
| > | 246 | ShakeNMake.INSTALL.DESTDIR = $(DESTDIR) | ||
| > | 247 | ShakeNMake.INSTALL.PREFIX = $(prefix) | ||
| > | 248 | ShakeNMake.INSTALL_ROOT=$(DESTDIR)$(prefix)/ | ||
| > | 249 | |||
| > | 250 | ######################################################################## | ||
| > | 251 | # ShakeNMake.CALL.FIND_BIN call()able function: | ||
| > | 252 | # $1 = app name | ||
| > | 253 | # $2 = optional path | ||
| > | 254 | ShakeNMake.CALL.FIND_BIN = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(2) $(PATH))))) | ||
| > | 255 | ######################################################################## | ||
| > | 256 | |||
| > | 257 | ######################################################################## | ||
| > | 258 | # Find some common binaries... | ||
| > | 259 | ShakeNMake.BINS.TAR := $(call ShakeNMake.CALL.FIND_BIN,tar) | ||
| > | 260 | ifeq (,$(ShakeNMake.BINS.TAR)) | ||
| > | 261 | ShakeNMake.BINS.TAR := $(call ShakeNMake.CALL.FIND_BIN,gtar) | ||
| > | 262 | endif | ||
| > | 263 | ShakeNMake.BINS.ZIP := $(call ShakeNMake.CALL.FIND_BIN,zip) | ||
| > | 264 | ShakeNMake.BINS.RM := $(call ShakeNMake.CALL.FIND_BIN,rm) | ||
| > | 265 | ShakeNMake.BINS.GCC := $(call ShakeNMake.CALL.FIND_BIN,gcc) | ||
| > | 266 | # | ||
| > | 267 | ######################################################################## | ||
| > | 268 | |||
| > | 269 | ######################################################################## | ||
| > | 270 | # An internal hack to enable "quiet" output. $(1) is a string which | ||
| > | 271 | # is shown ONLY if ShakeNMake.QUIET!=1 | ||
| > | 272 | ShakeNMake.QUIET ?= 0 | ||
| > | 273 | define ShakeNMake.CALL.SETX | ||
| > | 274 | if [[ x1 = "x$(ShakeNMake.QUIET)" ]]; then echo $(1); else set -x; fi | ||
| > | 275 | endef | ||
| > | 276 | ######################################################################## | ||
| > | 277 | |||
| > | 278 | ######################################################################## | ||
| > | 279 | # PACKAGE.DIST_FILES stuff... | ||
| > | 280 | ifeq (,$(ShakeNMake.BINS.TAR)) | ||
| > | 281 | dist: | ||
| > | 282 | @echo "'tar' was not found in the PATH, so i cannot build a dist tarball :(." | ||
| > | 283 | else | ||
| > | 284 | ShakeNMake.TARBALL.FLAGS ?= czf | ||
| > | 285 | ShakeNMake.TARBALL.XFLAGS ?= | ||
| > | 286 | ShakeNMake.TARBALL.BASENAME = $(PACKAGE.NAME)-$(PACKAGE.VERSION) | ||
| > | 287 | ShakeNMake.TARBALL.FILE = $(ShakeNMake.TARBALL.BASENAME).tar.gz | ||
| > | 288 | ShakeNMake.TARBALL.ZIPFILE = $(ShakeNMake.TARBALL.BASENAME).zip | ||
| > | 289 | DISTCLEAN_FILES += $(ShakeNMake.TARBALL.FILE) $(ShakeNMake.TARBALL.ZIPFILE) | ||
| > | 290 | |||
| > | 291 | dist-cleanup: | ||
| > | 292 | @rm -fr $(ShakeNMake.TARBALL.BASENAME); true | ||
| > | 293 | dist-target-implementation: | ||
| > | 294 | @-if test -d $(ShakeNMake.TARBALL.BASENAME); then rm -fr $(ShakeNMake.TARBALL.BASENAME) || exit $$?; fi | ||
| > | 295 | @echo "Creating $(ShakeNMake.TARBALL.FILE)..." | ||
| > | 296 | @mkdir $(ShakeNMake.TARBALL.BASENAME) | ||
| > | 297 | @cp --parents -r $(PACKAGE.DIST_FILES) $(ShakeNMake.TARBALL.BASENAME) | ||
| > | 298 | @find $(ShakeNMake.TARBALL.BASENAME) -name CVS -o -name .svn -type d | xargs rm -fr; true | ||
| > | 299 | @$(ShakeNMake.BINS.TAR) \ | ||
| > | 300 | $(ShakeNMake.TARBALL.FLAGS) \ | ||
| > | 301 | $(ShakeNMake.TARBALL.FILE) \ | ||
| > | 302 | $(ShakeNMake.TARBALL.XFLAGS) \ | ||
| > | 303 | $(ShakeNMake.TARBALL.BASENAME) | ||
| > | 304 | @ls -la $(ShakeNMake.TARBALL.FILE) | ||
| > | 305 | ifneq (,$(ShakeNMake.BINS.ZIP)) | ||
| > | 306 | @test -e $(ShakeNMake.TARBALL.ZIPFILE) && rm -f $(ShakeNMake.TARBALL.ZIPFILE); true | ||
| > | 307 | @$(ShakeNMake.BINS.ZIP) -q -r $(ShakeNMake.TARBALL.ZIPFILE) $(ShakeNMake.TARBALL.BASENAME) | ||
| > | 308 | @ls -la $(ShakeNMake.TARBALL.BASENAME).zip | ||
| > | 309 | endif | ||
| > | 310 | dist: | ||
| > | 311 | @$(MAKE) --no-print-directory dist-target-implementation; err=$$?; \ | ||
| > | 312 | $(MAKE) --no-print-directory dist-cleanup; echo $$err | ||
| > | 313 | clean: dist-cleanup | ||
| > | 314 | endif # if $(ShakeNMake.BINS.TAR) | ||
| > | 315 | # end dist | ||
| > | 316 | ######################################################################## | ||
| > | 317 | |||
| > | 318 | |||
| > | 319 | ######################################################################## | ||
| > | 320 | # ShakeNMake.CALL.INSTALL: $(call)able function: | ||
| > | 321 | # $1 = destination dir. | ||
| > | 322 | # $2 = list of source files or directories | ||
| > | 323 | # Copies $2 to $1. | ||
| > | 324 | ShakeNMake.CALL.INSTALL = { \ | ||
| > | 325 | test x = "x$(1)" && { echo "Install path is empty!"; exit 1; }; \ | ||
| > | 326 | test -d $(1) || mkdir -p $(1) || exit; \ | ||
| > | 327 | for x in $(2); do \ | ||
| > | 328 | echo -e '\t--> '$$x; \ | ||
| > | 329 | cp -rp $$x $(1) || { err=$$?; echo "Copy failed!"; exit $$?; }; \ | ||
| > | 330 | done; \ | ||
| > | 331 | } | ||
| > | 332 | # ShakeNMake.CALL.UNINSTALL: $(call)able function: | ||
| > | 333 | # $1 = destination dir. | ||
| > | 334 | # $2 = list of files (not directories!) to delete | ||
| > | 335 | # Removes $2 from $1. It tries to rmdir $1 when it is done, but that will | ||
| > | 336 | # only work if $1 is empty. Such a failure is silently ignored. | ||
| > | 337 | ShakeNMake.CALL.UNINSTALL = { \ | ||
| > | 338 | test -d $(1) || exit 0; \ | ||
| > | 339 | for x in $(2); do \ | ||
| > | 340 | echo -e "\t<-- " $(1)/$$x; \ | ||
| > | 341 | rm -f $(1)/$$x || exit; \ | ||
| > | 342 | done; \ | ||
| > | 343 | }; \ | ||
| > | 344 | rmdir $(1) 2>/dev/null || true; | ||
| > | 345 | # Trivia: i actually did delete all files in my home dir | ||
| > | 346 | # once while testing the uninstall code. Luckily, -r wasn't | ||
| > | 347 | # in effect. | ||
| > | 348 | ######################################################################## | ||
| > | 349 | |||
| > | 350 | ######################################################################## | ||
| > | 351 | # install-% installs files listed in $(INSTALL.%) to $(INSTALL.%.DEST), | ||
| > | 352 | # which defaults to %. $(ShakeNMake.INSTALL.DESTDIR) is automatically | ||
| > | 353 | # prefixed to installation paths, for compatibility with the GNU | ||
| > | 354 | # Autotools and Debian-preferred install methods. | ||
| > | 355 | # When specifying install paths for client code, like so: | ||
| > | 356 | # | ||
| > | 357 | # INSTALL.MYSTUFF = list of files | ||
| > | 358 | # INSTALL.MYSTUFF.DEST = $(prefix)/$(PACKAGE.NAME) | ||
| > | 359 | # | ||
| > | 360 | # the path should always be relative to $(prefix), with the assumption that | ||
| > | 361 | # $(prefix) is an absolute path without a trailing backslash. The install | ||
| > | 362 | # path should NOT take into accont $(DESTDIR), because that is handled | ||
| > | 363 | # transparently at a deeper level. | ||
| > | 364 | ShakeNMake.CALL.GET_INSTALL_DEST = $(ShakeNMake.INSTALL.DESTDIR)$(INSTALL.$(1).DEST) | ||
| > | 365 | install-%: | ||
| > | 366 | @echo "Installing \$$(INSTALL.$(*)) files to $(call ShakeNMake.CALL.GET_INSTALL_DEST,$(*))..."; \ | ||
| > | 367 | $(call ShakeNMake.CALL.INSTALL,$(call ShakeNMake.CALL.GET_INSTALL_DEST,$(*)),$(INSTALL.$(*))) | ||
| > | 368 | # Maintenance note: don't break the $(call) args onto separate lines because | ||
| > | 369 | # that introduces a whitespace char which can screw up the install code. :( | ||
| > | 370 | ######################################################################## | ||
| > | 371 | # uninstall-% deletes INSTALL.% from INSTALL.%.DEST, which defaults to | ||
| > | 372 | # $(ShakeNMake.INSTALL_ROOT)%. | ||
| > | 373 | uninstall-%: | ||
| > | 374 | @echo "Uninstalling \$$(INSTALL.$(*)) files from $(call ShakeNMake.CALL.GET_INSTALL_DEST,$(*))..."; \ | ||
| > | 375 | $(call ShakeNMake.CALL.UNINSTALL,$(call ShakeNMake.CALL.GET_INSTALL_DEST,$(*)),$(INSTALL.$(*))) | ||
| > | 376 | # Maintenance note: don't break the $(call) args onto separate lines because | ||
| > | 377 | # that introduces a whitespace char which can screw up the uninstall code. :( | ||
| > | 378 | ######################################################################## | ||
| > | 379 | |||
| > | 380 | ######################################################################## | ||
| > | 381 | # ShakeNMake.EVAL.RULES.INSTALL adds [un]install-$(1) as prerequisites | ||
| > | 382 | # of the [un]install targets, so that they get called by | ||
| > | 383 | # 'make [un]install'. | ||
| > | 384 | define ShakeNMake.EVAL.RULES.INSTALL | ||
| > | 385 | INSTALL.$(1).DEST ?= $(prefix)/$(1) | ||
| > | 386 | install: install-$(1) | ||
| > | 387 | uninstall: uninstall-$(1) | ||
| > | 388 | endef | ||
| > | 389 | ######################################################################## | ||
| > | 390 | # $(call ShakeNMake.CALL.RULES.INSTALL,[list]) calls and $(eval)s | ||
| > | 391 | # ShakeNMake.EVAL.RULES.INSTALL one time for each item in $(1). | ||
| > | 392 | define ShakeNMake.CALL.RULES.INSTALL | ||
| > | 393 | $(foreach proggy,$(1),$(eval $(call ShakeNMake.EVAL.RULES.INSTALL,$(proggy)))) | ||
| > | 394 | endef | ||
| > | 395 | # end ShakeNMake.EVAL.RULES.INSTALL | ||
| > | 396 | ######################################################################## | ||
| > | 397 | |||
| > | 398 | |||
| > | 399 | ######################################################################## | ||
| > | 400 | # builds %.o from %.c using $(CC). | ||
| > | 401 | # Passes on flags from these vars: | ||
| > | 402 | # CFLAGS, %.CFLAGS | ||
| > | 403 | # INCLUDES, %.OBJ.INCLUDES | ||
| > | 404 | # CPPFLAGS, %.OBJ.CPPFLAGS | ||
| > | 405 | %.o: %.c $(ShakeNMake.MAKEFILE) $(PACKAGE.MAKEFILE) | ||
| > | 406 | @$(call ShakeNMake.CALL.SETX,"CC [$@] ..."); \ | ||
| > | 407 | $(CC) $(CFLAGS) $($(*).OBJ.CFLAGS) \ | ||
| > | 408 | $(INCLUDES) $($(*).OBJ.INCLUDES) \ | ||
| > | 409 | $(CPPFLAGS) $($(*).OBJ.CPPFLAGS) \ | ||
| > | 410 | -c -o $@ $< | ||
| > | 411 | ######################################################################## | ||
| > | 412 | |||
| > | 413 | ######################################################################## | ||
| > | 414 | # build %.o from %.cpp using $(CXX). | ||
| > | 415 | # Passes on flags from these vars: | ||
| > | 416 | # CXXFLAGS, %.OBJ.CXXFLAGS | ||
| > | 417 | # INCLUDES, %.OBJ.INCLUDES | ||
| > | 418 | # CPPFLAGS, %.OBJ.CPPFLAGS | ||
| > | 419 | %.o: %.cpp $(ShakeNMake.MAKEFILE) $(PACKAGE.MAKEFILE) | ||
| > | 420 | @$(call ShakeNMake.CALL.SETX,"CXX [$@] ..."); \ | ||
| > | 421 | $(CXX) $(CXXFLAGS) $($(*).OBJ.CXXFLAGS) \ | ||
| > | 422 | $(INCLUDES) $($(*).OBJ.INCLUDES) \ | ||
| > | 423 | $(CPPFLAGS) $($(*).OBJ.CPPFLAGS) \ | ||
| > | 424 | -c -o $@ $< | ||
| > | 425 | # end %.o: %.cpp rules | ||
| > | 426 | ######################################################################## | ||
| > | 427 | |||
| > | 428 | |||
| > | 429 | ######################################################################## | ||
| > | 430 | # ShakeNMake.EVAL.RULES.BIN is intended to be called like so: | ||
| > | 431 | # $(eval $(call ShakeNMake.EVAL.RULES.BIN,MyApp)) | ||
| > | 432 | # | ||
| > | 433 | # It builds a binary named $(1) by running $(CC) and passing it: | ||
| > | 434 | # | ||
| > | 435 | # INCLUDES, $(1).BIN.INCLUDES | ||
| > | 436 | # CFLAGS, $(1).BIN.CFLAGS | ||
| > | 437 | # CXXFLAGS, $(1).BIN.CXXFLAGS | ||
| > | 438 | # CPPFLAGS, $(1).BIN.CPPFLAGS | ||
| > | 439 | # LDFLAGS, $(1).BIN.LDFLAGS | ||
| > | 440 | # $(1).BIN.OBJECTS $(1).BIN.SOURCES | ||
| > | 441 | # | ||
| > | 442 | # Note that we have to pass both CFLAGS and CPPFLAGS because .SOURCES might | ||
| > | 443 | # contain either of C or C++ files. | ||
| > | 444 | define ShakeNMake.EVAL.RULES.BIN | ||
| > | 445 | $(1).BIN = $(1)$(ShakeNMake.EXTENSIONS.EXE) | ||
| > | 446 | $(1).BIN: $$($(1).BIN) | ||
| > | 447 | # Many developers feel that bins should not be cleaned by 'make | ||
| > | 448 | # clean', but instead by distclean, but i'm not one of those | ||
| > | 449 | # developers. i subscribe more to the school of thought that distclean | ||
| > | 450 | # is for cleaning up configure-created files. That said, shake-n-make | ||
| > | 451 | # isn't designed to use a configure-like process, so that is probably | ||
| > | 452 | # moot here and we probably (maybe?) should clean up bins only in | ||
| > | 453 | # distclean. As always: hack it to suit your preference: | ||
| > | 454 | CLEAN_FILES += $$($(1).BIN) | ||
| > | 455 | $$($(1).BIN): $$($(1).BIN.OBJECTS) $$($(1).BIN.SOURCES) | ||
| > | 456 | @test x = "x$$($(1).BIN.OBJECTS)$$($(1).BIN.SOURCES)" && { \ | ||
| > | 457 | echo "$(1).BIN.OBJECTS and/or $(1).BIN.SOURCES is undefined!"; exit 1; }; \ | ||
| > | 458 | $(call ShakeNMake.CALL.SETX,"CXX [$$@] ..."); \ | ||
| > | 459 | $$(CXX) -o $$@ \ | ||
| > | 460 | $$(INCLUDES) $$($(1).BIN.INCLUDES) \ | ||
| > | 461 | $$(CFLAGS) $$($(1).BIN.CFLAGS) \ | ||
| > | 462 | $$(CXXFLAGS) $$($(1).BIN.CXXFLAGS) \ | ||
| > | 463 | $$(CPPFLAGS) $$($(1).BIN.CPPFLAGS) \ | ||
| > | 464 | $$($(1).BIN.OBJECTS) $$($(1).BIN.SOURCES) \ | ||
| > | 465 | $$(LDFLAGS) $$($(1).BIN.LDFLAGS) | ||
| > | 466 | # note about 'set -x': i do this because it normalizes backslashed | ||
| > | 467 | # newline, extra spaces, and other oddities of formatting. | ||
| > | 468 | endef | ||
| > | 469 | ######################################################################## | ||
| > | 470 | # $(call ShakeNMake.CALL.RULES.BINS,[list]) calls and $(eval)s | ||
| > | 471 | # ShakeNMake.EVAL.RULES.BIN for each entry in $(1) | ||
| > | 472 | define ShakeNMake.CALL.RULES.BINS | ||
| > | 473 | $(foreach bin,$(1),$(eval $(call ShakeNMake.EVAL.RULES.BIN,$(bin)))) | ||
| > | 474 | endef | ||
| > | 475 | # end ShakeNMake.CALL.RULES.BIN and friends | ||
| > | 476 | ######################################################################## | ||
| > | 477 | |||
| > | 478 | |||
| > | 479 | ######################################################################## | ||
| > | 480 | # ShakeNMake.EVAL.RULES.DLL builds builds $(1)$(ShakeNMake.EXTENSIONS.DLL) from object files | ||
| > | 481 | # defined by $(1).DLL.OBJECTS and $(1).DLL.SOURCES. Flags passed on | ||
| > | 482 | # to the linker include: | ||
| > | 483 | # LDFLAGS, $(1).DLL.LDFLAGS, LDADD, -shared -export-dynamic | ||
| > | 484 | # $(1).DLL.CPPFLAGS | ||
| > | 485 | # | ||
| > | 486 | # Also defines the var $(1).DLL, which expands to the filename of the DLL, | ||
| > | 487 | # (normally $(1)$(ShakeNMake.EXTENSIONS.DLL)). | ||
| > | 488 | define ShakeNMake.EVAL.RULES.DLL | ||
| > | 489 | $(1).DLL = $(1)$(ShakeNMake.EXTENSIONS.DLL) | ||
| > | 490 | ifneq (.DLL,$(ShakeNMake.EXTENSIONS.DLL)) | ||
| > | 491 | $(1).DLL: $$($(1).DLL) | ||
| > | 492 | endif | ||
| > | 493 | CLEAN_FILES += $$($(1).DLL) | ||
| > | 494 | $$($(1).DLL): $$($(1).DLL.SOURCES) $$($(1).DLL.OBJECTS) | ||
| > | 495 | @test x = "x$$($(1).DLL.OBJECTS)$$($(1).DLL.SOURCES)" && { \ | ||
| > | 496 | echo "$(1).DLL.OBJECTS and/or $(1).DLL.SOURCES are/is undefined!"; exit 1; }; \ | ||
| > | 497 | $(call ShakeNMake.CALL.SETX,"CXX [$$@] ..."); \ | ||
| > | 498 | $$(CXX) -o $$@ -shared -export-dynamic $$(LDFLAGS) \ | ||
| > | 499 | $$($(1).DLL.LDFLAGS) $$($(1).DLL.OBJECTS) $$($(1).DLL.SOURCES) \ | ||
| > | 500 | $$($(1).DLL.CPPFLAGS) | ||
| > | 501 | endef | ||
| > | 502 | ######################################################################## | ||
| > | 503 | # $(call ShakeNMake.CALL.RULES.DLLS,[list]) calls and $(eval)s | ||
| > | 504 | # ShakeNMake.EVAL.RULES.DLL for each entry in $(1) | ||
| > | 505 | define ShakeNMake.CALL.RULES.DLLS | ||
| > | 506 | $(foreach dll,$(1),$(eval $(call ShakeNMake.EVAL.RULES.DLL,$(dll)))) | ||
| > | 507 | endef | ||
| > | 508 | # end ShakeNMake.CALL.RULES.DLLS and friends | ||
| > | 509 | ######################################################################## | ||
| > | 510 | |||
| > | 511 | ######################################################################## | ||
| > | 512 | # ShakeNMake.EVAL.RULES.LIB creates rules to build static library | ||
| > | 513 | # $(1).a | ||
| > | 514 | define ShakeNMake.EVAL.RULES.LIB | ||
| > | 515 | $(1).LIB = $(1).a | ||
| > | 516 | $(1).LIB: $$($(1).LIB) | ||
| > | 517 | CLEAN_FILES += $$($(1).LIB) | ||
| > | 518 | $$($(1).LIB): $$($(1).LIB.OBJECTS) | ||
| > | 519 | @$(call ShakeNMake.CALL.SETX,"AR [$$@] ..."); \ | ||
| > | 520 | $$(AR) crs $$@ $$($(1).LIB.OBJECTS) | ||
| > | 521 | endef | ||
| > | 522 | define ShakeNMake.CALL.RULES.LIBS | ||
| > | 523 | $(foreach liba,$(1),$(eval $(call ShakeNMake.EVAL.RULES.LIB,$(liba)))) | ||
| > | 524 | endef | ||
| > | 525 | # end ShakeNMake.EVAL.RULES.LIB | ||
| > | 526 | ######################################################################## | ||
| > | 527 | |||
| > | 528 | ######################################################################## | ||
| > | 529 | # [DIST]CLEAN_FILES support... | ||
| > | 530 | ######################################################################## | ||
| > | 531 | CLEAN_FILES += *.o | ||
| > | 532 | DISTCLEAN_FILES += *~ | ||
| > | 533 | clean: ShakeNMake.FORCE | ||
| > | 534 | @fl="$(sort $(wildcard $(CLEAN_FILES)))"; \ | ||
| > | 535 | test x = "x$$fl" && { echo "Nothing to clean!"; exit 0; }; \ | ||
| > | 536 | $(call ShakeNMake.CALL.SETX,"Cleaning up ..."); \ | ||
| > | 537 | $(ShakeNMake.BINS.RM) -fr $$fl | ||
| > | 538 | distclean: ShakeNMake.FORCE | ||
| > | 539 | @fl="$(sort $(wildcard $(CLEAN_FILES) $(DISTCLEAN_FILES)))"; \ | ||
| > | 540 | test x = "x$$fl" && { echo "Nothing to clean!"; exit 0; }; \ | ||
| > | 541 | $(call ShakeNMake.CALL.SETX,"Cleaning up ..."); \ | ||
| > | 542 | $(ShakeNMake.BINS.RM) -fr $$fl | ||
| > | 543 | # end [DIST]CLEAN_FILES | ||
| > | 544 | ######################################################################## | ||
| > | 545 | |||
| > | 546 | |||
| > | 547 | ######################################################################## | ||
| > | 548 | # Automatic dependencies generation for C/C++ code... | ||
| > | 549 | # To disable deps generation, set ShakeNMake.USE_MKDEPS=0 *before* | ||
| > | 550 | # including this file. | ||
| > | 551 | ifeq (,$(ShakeNMake.BINS.GCC)) | ||
| > | 552 | ShakeNMake.USE_MKDEPS ?= 0 | ||
| > | 553 | else | ||
| > | 554 | ShakeNMake.USE_MKDEPS ?= 1 | ||
| > | 555 | endif | ||
| > | 556 | #$(warning ShakeNMake.USE_MKDEPS=$(ShakeNMake.USE_MKDEPS)); | ||
| > | 557 | ifeq (1,$(ShakeNMake.USE_MKDEPS)) | ||
| > | 558 | ShakeNMake.CISH_SOURCES += $(wildcard *.cpp *.c *.c++ *.h *.hpp *.h++ *.hh) | ||
| > | 559 | #$(warning ShakeNMake.CISH_SOURCES=$(ShakeNMake.CISH_SOURCES)) | ||
| > | 560 | ifneq (,$(ShakeNMake.CISH_SOURCES)) | ||
| > | 561 | ShakeNMake.CISH_DEPS_FILE := .make.c_deps | ||
| > | 562 | ShakeNMake.BINS.MKDEP = gcc -E -MM $(INCLUDES) | ||
| > | 563 | CLEAN_FILES += $(ShakeNMake.CISH_DEPS_FILE) | ||
| > | 564 | $(ShakeNMake.CISH_DEPS_FILE): $(PACKAGE.MAKEFILE) $(ShakeNMake.MAKEFILE) $(ShakeNMake.CISH_SOURCES) | ||
| > | 565 | @$(ShakeNMake.BINS.MKDEP) $(ShakeNMake.CISH_SOURCES) 2>/dev/null > $@ || \ | ||
| > | 566 | $(ShakeNMake.BINS.RM) -f $@ 2>/dev/null | ||
| > | 567 | # ^^^^ We rm -f the deps file if mkdep fails because we don't want a bad generated makefile | ||
| > | 568 | # to kill the build. | ||
| > | 569 | |||
| > | 570 | ifneq (,$(strip $(filter distclean clean,$(MAKECMDGOALS)))) | ||
| > | 571 | #$(warning Skipping C/C++ deps generation.) | ||
| > | 572 | ABSOLUTEBOGO := $(shell $(ShakeNMake.BINS.RM) -f $(ShakeNMake.CISH_DEPS_FILE)) | ||
| > | 573 | else | ||
| > | 574 | #$(warning Including C/C++ deps.) | ||
| > | 575 | -include $(ShakeNMake.CISH_DEPS_FILE) | ||
| > | 576 | endif | ||
| > | 577 | |||
| > | 578 | endif | ||
| > | 579 | # ^^^^ ifneq(,$(ShakeNMake.CISH_SOURCES)) | ||
| > | 580 | endif | ||
| > | 581 | # ^^^^ end $(ShakeNMake.USE_MKDEPS) | ||
| > | 582 | ######################################################################## | ||
| > | 583 | |||
| > | 584 | ######################################################################## | ||
| > | 585 | # Doxygen | ||
| > | 586 | ShakeNMake.BINS.SED := $(call ShakeNMake.CALL.FIND_BIN,sed) | ||
| > | 587 | ShakeNMake.BINS.PERL := $(call ShakeNMake.CALL.FIND_BIN,perl) | ||
| > | 588 | ShakeNMake.BINS.LATEX := $(call ShakeNMake.CALL.FIND_BIN,latex) | ||
| > | 589 | ifneq (,$(ShakeNMake.BINS.PERL)) | ||
| > | 590 | ShakeNMake.BINS.DOXYGEN := $(call ShakeNMake.CALL.FIND_BIN,doxygen) | ||
| > | 591 | ifneq (,$(ShakeNMake.BINS.DOXYGEN)) | ||
| > | 592 | ShakeNMake.DOXYGEN.DOXYFILE_TEMPLATE = Doxyfile.at | ||
| > | 593 | ifneq (,$(wildcard $(ShakeNMake.DOXYGEN.DOXYFILE_TEMPLATE))) | ||
| > | 594 | ShakeNMake.DOXYGEN.INDEX := $(wildcard Doxygen-index.txt) | ||
| > | 595 | #ifneq (,$(wildcard $(ShakeNMake.DOXYGEN.INDEX))) | ||
| > | 596 | ######################################################################## | ||
| > | 597 | # let's try to do doxygen stuff... | ||
| > | 598 | ######################################################################## | ||
| > | 599 | # Set ShakeNMake.DOXYGEN.USE_DOT to 1 if you have 'dot' and want to | ||
| > | 600 | # use it in the doxygen stuff. It slows down the doc gen process | ||
| > | 601 | # significantly, but it looks nice. | ||
| > | 602 | ShakeNMake.DOXYGEN.USE_DOT ?= 0 | ||
| > | 603 | ################################################## | ||
| > | 604 | PACKAGE.DIST_FILES += $(ShakeNMake.DOXYGEN.DOXYFILE_TEMPLATE) $(ShakeNMake.DOXYGEN.INDEX) | ||
| > | 605 | |||
| > | 606 | ShakeNMake.DOXYGEN.INCLUDE_DIRS = . | ||
| > | 607 | |||
| > | 608 | ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML = $(PACKAGE.NAME)-$(PACKAGE.VERSION)-doxygen-html | ||
| > | 609 | ShakeNMake.DOXYGEN.OUTPUT_DIR.LATEX = $(PACKAGE.NAME)-$(PACKAGE.VERSION)-doxygen-latex | ||
| > | 610 | |||
| > | 611 | |||
| > | 612 | ifneq (,$(ShakeNMake.BINS.LATEX)) | ||
| > | 613 | ShakeNMake.DOXYGEN.GENERATE_LATEX ?= YES | ||
| > | 614 | else | ||
| > | 615 | ShakeNMake.DOXYGEN.GENERATE_LATEX ?= NO | ||
| > | 616 | endif | ||
| > | 617 | |||
| > | 618 | Doxyfile: $(ShakeNMake.DOXYGEN.DOXYFILE_TEMPLATE) $(ShakeNMake.MAKEFILE) $(PACKAGE.MAKEFILE) | ||
| > | 619 | @$(ShakeNMake.BINS.SED) -e 's,@PACKAGE_NAME@,$(PACKAGE.NAME),' \ | ||
| > | 620 | -e 's,@PACKAGE_VERSION@,$(PACKAGE.VERSION),' \ | ||
| > | 621 | -e 's,@DOXYGEN_INPUT@,$(ShakeNMake.DOXYGEN.INDEX) $(ShakeNMake.DOXYGEN.INCLUDE_DIRS),' \ | ||
| > | 622 | -e 's,@HTML_OUTPUT@,$(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML),' \ | ||
| > | 623 | -e 's,@LATEX_OUTPUT@,$(ShakeNMake.DOXYGEN.OUTPUT_DIR.LATEX),' \ | ||
| > | 624 | -e 's,@GENERATE_LATEX@,$(ShakeNMake.DOXYGEN.GENERATE_LATEX),' \ | ||
| > | 625 | -e 's,@PERL@,$(ShakeNMake.BINS.PERL),' \ | ||
| > | 626 | -e 's,@USE_DOT@,$(ShakeNMake.DOXYGEN.USE_DOT),' \ | ||
| > | 627 | < $(ShakeNMake.DOXYGEN.DOXYFILE_TEMPLATE) > $@ | ||
| > | 628 | |||
| > | 629 | doxygen-clean: | ||
| > | 630 | @test -d $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML) && rm -fr $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML); \ | ||
| > | 631 | rm -f Doxyfile; \ | ||
| > | 632 | true | ||
| > | 633 | |||
| > | 634 | .PHONY: $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML) | ||
| > | 635 | $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML): Doxyfile | ||
| > | 636 | @echo "Building docs from headers" | ||
| > | 637 | $(ShakeNMake.BINS.DOXYGEN) | ||
| > | 638 | @echo "Output should be in the directory '$@'." | ||
| > | 639 | ifneq (NO,$(ShakeNMake.DOXYGEN.GENERATE_LATEX)) | ||
| > | 640 | @echo "Latex output (if any) is in '$(ShakeNMake.DOXYGEN.OUTPUT_DIR.LATEX)'." | ||
| > | 641 | endif | ||
| > | 642 | |||
| > | 643 | doxygen: $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML) | ||
| > | 644 | |||
| > | 645 | doxygen-dist: doxygen | ||
| > | 646 | tar czf $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML).tar.gz $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML) | ||
| > | 647 | @ls -la $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML).tar.gz | ||
| > | 648 | |||
| > | 649 | dist: doxygen-dist | ||
| > | 650 | CLEAN_FILES += Doxyfile $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML) latex | ||
| > | 651 | |||
| > | 652 | |||
| > | 653 | INSTALL_DOXYGEN = $(ShakeNMake.DOXYGEN.OUTPUT_DIR.HTML) | ||
| > | 654 | INSTALL_DOXYGEN_DEST = $(prefix)/share/doc/$(PACKAGE.NAME) | ||
| > | 655 | install: doxygen | ||
| > | 656 | $(call ShakeNMake.CALL.RULES.INSTALL,DOXYGEN) | ||
| > | 657 | |||
| > | 658 | ################################################## | ||
| > | 659 | #endif # $(ShakeNMake.DOXYGEN.INDEX) | ||
| > | 660 | endif # $(ShakeNMake.DOXYGEN.DOXYFILE_TEMPLATE) | ||
| > | 661 | endif # $(ShakeNMake.BINS.DOXYGEN) | ||
| > | 662 | endif # $(ShakeNMake.BINS.PERL) | ||
| > | 663 | # end Doxygen | ||
| > | 664 | ######################################################################## | ||
| > | 665 | |||
Added test.c
| Old () | New (c4bd24e3ba1c76f7) | |||
|---|---|---|---|---|
| > | 1 | #include <stdio.h> | ||
| > | 2 | #include <stdlib.h> | ||
| > | 3 | #include <string.h> | ||
| > | 4 | #include <ctype.h> | ||
| > | 5 | #include "whrc.h" | ||
| > | 6 | |||
| > | 7 | #if 1 | ||
| > | 8 | #include <stdio.h> // only for debuggering | ||
| > | 9 | #define MARKER if(1) printf("MARKER: %s:%d:%s():\n",__FILE__,__LINE__,__func__); if(1) printf | ||
| > | 10 | #else | ||
| > | 11 | static void bogo_printf(char const * fmt, ...) {} | ||
| > | 12 | #define MARKER if(0) bogo_printf | ||
| > | 13 | #endif | ||
| > | 14 | |||
| > | 15 | int test_one() | ||
| > | 16 | { | ||
| > | 17 | MARKER("starting test\n"); | ||
| > | 18 | whrc_context * cx = whrc_create_context(); | ||
| > | 19 | #define SLEN 20 | ||
| > | 20 | char * str = (char *)malloc(SLEN+1); | ||
| > | 21 | memset( str, 'x', SLEN ); | ||
| > | 22 | str[SLEN] = 0; | ||
| > | 23 | #undef SLEN | ||
| > | 24 | whrc_register( cx, str, free ); | ||
| > | 25 | size_t rc = whrc_refcount(cx,str); // rc == 1 | ||
| > | 26 | MARKER("refcount=%u\n",rc); | ||
| > | 27 | rc = whrc_addref(cx,str); //rc == 2 | ||
| > | 28 | MARKER("refcount=%u\n",rc); | ||
| > | 29 | rc = whrc_rmref(cx,str); // rc == 1 | ||
| > | 30 | MARKER("refcount=%u\n",rc); | ||
| > | 31 | rc = whrc_rmref(cx,str); // rc == 0, free(str) is called | ||
| > | 32 | MARKER("refcount=%u\n",rc); | ||
| > | 33 | rc = whrc_rmref(cx,str); // rc == 0, free(str) is called | ||
| > | 34 | MARKER("refcount=%u\n",rc); | ||
| > | 35 | //whrc_destroy_context(cx,true); | ||
| > | 36 | MARKER("ending test\n"); | ||
| > | 37 | return 0; | ||
| > | 38 | } | ||
| > | 39 | |||
| > | 40 | int main( int argc, char ** argv ) | ||
| > | 41 | { | ||
| > | 42 | int rc = 0; | ||
| > | 43 | rc = test_one(); | ||
| > | 44 | printf("Done rc=%d=[%s].\n",rc, | ||
| > | 45 | (0==rc) | ||
| > | 46 | ? "You win :)" | ||
| > | 47 | : "You lose :("); | ||
| > | 48 | return rc; | ||
| > | 49 | } | ||
Added whgc.c
| Old () | New (d5dd771fe2deaaa7) | |||
|---|---|---|---|---|
| > | 1 | #include <stdlib.h> | ||
| > | 2 | #include <string.h> /* memset() */ | ||
| > | 3 | #include "whgc.h" | ||
| > | 4 | #include "whhash.h" | ||
| > | 5 | |||
| > | 6 | /** | ||
| > | 7 | Partial list of changes changes by Stephan Beal: | ||
| > | 8 | |||
| > | 9 | - from unsigned int to unsigned long for hash keys. | ||
| > | 10 | |||
| > | 11 | - created typedef whhash_val_t. | ||
| > | 12 | |||
| > | 13 | - Added whhash_hash_cstring_{djb2,sdbm}() algorithms, | ||
| > | 14 | taken from: http://www.cse.yorku.ca/~oz/hash.html | ||
| > | 15 | |||
| > | 16 | - whhash_iter() now returns 0 if the whhash_table | ||
| > | 17 | is empty, simplifying iteration error checking a bit. | ||
| > | 18 | |||
| > | 19 | - The API is now const-safe, insofar as feasible. That is, funcs | ||
| > | 20 | which can get away with (void const *) instead of (void*) now use | ||
| > | 21 | const parameters. | ||
| > | 22 | |||
| > | 23 | - Added ability to set a custom key/value dtors for each | ||
| > | 24 | whhash_table. | ||
| > | 25 | |||
| > | 26 | - Slightly changed semantics of some routines to accommodate the | ||
| > | 27 | dtor handling. | ||
| > | 28 | |||
| > | 29 | - Default dtor for keys is now null instead of free. | ||
| > | 30 | */ | ||
| > | 31 | |||
| > | 32 | |||
| > | 33 | |||
| > | 34 | #include <stdio.h> /* only for debuggering. */ | ||
| > | 35 | |||
| > | 36 | #if defined(__cplusplus) | ||
| > | 37 | extern "C" { | ||
| > | 38 | # include <cassert> | ||
| > | 39 | # define ARG_UNUSED(X) | ||
| > | 40 | #else | ||
| > | 41 | # include <assert.h> | ||
| > | 42 | # define ARG_UNUSED(X) X | ||
| > | 43 | #endif /* __cplusplus */ | ||
| > | 44 | |||
| > | 45 | #if 1 | ||
| > | 46 | #define MARKER printf("**** MARKER: %s:%d:%s():\n",__FILE__,__LINE__,__func__); | ||
| > | 47 | #else | ||
| > | 48 | #define MARKER printf("**** MARKER: %s:%d:\n",__FILE__,__LINE__); | ||
| > | 49 | #endif | ||
| > | 50 | |||
| > | 51 | /** | ||
| > | 52 | Define the constants for the whgc_events object... | ||
| > | 53 | */ | ||
| > | 54 | const whgc_events_t whgc_events = { | ||
| > | 55 | 1, /* registered */ | ||
| > | 56 | 2, /* unregistered */ | ||
| > | 57 | 3, /* destructing_item */ | ||
| > | 58 | 4, /* destructing_context */ | ||
| > | 59 | 10 /* last_event_id */ | ||
| > | 60 | }; | ||
| > | 61 | |||
| > | 62 | /** | ||
| > | 63 | Holder for generic gc data. | ||
| > | 64 | */ | ||
| > | 65 | struct whgc_gc_entry | ||
| > | 66 | { | ||
| > | 67 | void * key; | ||
| > | 68 | void * value; | ||
| > | 69 | whgc_dtor_f keyDtor; | ||
| > | 70 | whgc_dtor_f valueDtor; | ||
| > | 71 | struct whgc_gc_entry * left; | ||
| > | 72 | struct whgc_gc_entry * right; | ||
| > | 73 | }; | ||
| > | 74 | typedef struct whgc_gc_entry whgc_gc_entry; | ||
| > | 75 | #define WHGC_GC_ENTRY_INIT {0,0,0,0,0,0} | ||
| > | 76 | static const whgc_gc_entry whgc_gc_entry_init = WHGC_GC_ENTRY_INIT; | ||
| > | 77 | |||
| > | 78 | #define WHGC_STATS_INIT {\ | ||
| > | 79 | 0, /* entry_count */ \ | ||
| > | 80 | 0, /* reg_count */ \ | ||
| > | 81 | 0, /* unreg_count */ \ | ||
| > | 82 | 0 /* alloced */ \ | ||
| > | 83 | } | ||
| > | 84 | static const whgc_stats whgc_stats_init = WHGC_STATS_INIT; | ||
| > | 85 | |||
| > | 86 | struct whgc_listener | ||
| > | 87 | { | ||
| > | 88 | struct whgc_listener * next; | ||
| > | 89 | whgc_listener_f func; | ||
| > | 90 | }; | ||
| > | 91 | typedef struct whgc_listener whgc_listener; | ||
| > | 92 | #define WHGC_LISTENER_INIT {0,0} | ||
| > | 93 | static const whgc_listener whgc_listener_init = WHGC_LISTENER_INIT; | ||
| > | 94 | |||
| > | 95 | struct whgc_context | ||
| > | 96 | { | ||
| > | 97 | void const * client; | ||
| > | 98 | /** | ||
| > | 99 | Hashtable of (void*) to (whgc_gc_entry*) | ||
| > | 100 | */ | ||
| > | 101 | whhash_table * ht; | ||
| > | 102 | /** | ||
| > | 103 | Holds the right-most (most recently added) entry. A cleanup, | ||
| > | 104 | the list is walked leftwards to free the entries in reverse | ||
| > | 105 | order. | ||
| > | 106 | */ | ||
| > | 107 | whgc_gc_entry * current; | ||
| > | 108 | /** | ||
| > | 109 | Event listeners. | ||
| > | 110 | */ | ||
| > | 111 | whgc_listener * listeners; | ||
| > | 112 | /** | ||
| > | 113 | Informal stats. | ||
| > | 114 | */ | ||
| > | 115 | whgc_stats stats; | ||
| > | 116 | }; | ||
| > | 117 | |||
| > | 118 | #define WHGC_CONTEXT_INIT {\ | ||
| > | 119 | 0,/*client*/ \ | ||
| > | 120 | 0,/*ht*/ \ | ||
| > | 121 | 0,/*current*/ \ | ||
| > | 122 | 0,/*listeners*/ \ | ||
| > | 123 | WHGC_STATS_INIT} | ||
| > | 124 | static const whgc_context whgc_context_init = WHGC_CONTEXT_INIT; | ||
| > | 125 | |||
| > | 126 | /** | ||
| > | 127 | A destructor for use with the hashtable API. Calls | ||
| > | 128 | whhash_destroy((whhash_table*)k). | ||
| > | 129 | static void whgc_free_hashtable( void * k ) | ||
| > | 130 | { | ||
| > | 131 | //MARKER; printf("Freeing HASHTABLE @%p\n",k); | ||
| > | 132 | whhash_destroy( (whhash_table*)k ); | ||
| > | 133 | } | ||
| > | 134 | */ | ||
| > | 135 | |||
| > | 136 | /** | ||
| > | 137 | A logging version of free(). | ||
| > | 138 | */ | ||
| > | 139 | static void whgc_free( void * k ) | ||
| > | 140 | { | ||
| > | 141 | //MARKER; printf("Freeing GENERIC (void*) @%p\n",k); | ||
| > | 142 | free(k); | ||
| > | 143 | } | ||
| > | 144 | |||
| > | 145 | void * whgc_alloc( whgc_context * cx, size_t size, whgc_dtor_f dtor ) | ||
| > | 146 | { | ||
| > | 147 | void * ret = size ? malloc( size ) : 0; | ||
| > | 148 | if( ! ret ) return 0; | ||
| > | 149 | memset( ret, 0, size ); | ||
| > | 150 | if( !cx ) return ret; | ||
| > | 151 | cx->stats.alloced += size; | ||
| > | 152 | whgc_add( cx, ret, dtor ); | ||
| > | 153 | return ret; | ||
| > | 154 | } | ||
| > | 155 | |||
| > | 156 | void const * whgc_get_context_client(whgc_context const *cx) | ||
| > | 157 | { | ||
| > | 158 | return cx ? cx->client : 0; | ||
| > | 159 | } | ||
| > | 160 | /** | ||
| > | 161 | Calls all registered listeners with the given | ||
| > | 162 | parameters. | ||
| > | 163 | */ | ||
| > | 164 | static void whgc_fire_event( whgc_context const *cx, | ||
| > | 165 | short ev, | ||
| > | 166 | void const * key, | ||
| > | 167 | void const * val ) | ||
| > | 168 | { | ||
| > | 169 | //MARKER;printf("Firing event %d for cx @%p\n",ev,cx); | ||
| > | 170 | whgc_listener * l = cx ? cx->listeners : 0; | ||
| > | 171 | if( l ) | ||
| > | 172 | { | ||
| > | 173 | whgc_event E; | ||
| > | 174 | E.cx = cx; | ||
| > | 175 | E.type = ev; | ||
| > | 176 | E.key = key; | ||
| > | 177 | E.value = val; | ||
| > | 178 | while( l ) | ||
| > | 179 | { | ||
| > | 180 | if( l->func ) | ||
| > | 181 | { | ||
| > | 182 | //MARKER;printf("Firing @%p(cx=@%p,event=%d,key=@%p,val=@%p)\n",l->func,cx,ev,key,val); | ||
| > | 183 | l->func( &E ); | ||
| > | 184 | } | ||
| > | 185 | l = l->next; | ||
| > | 186 | } | ||
| > | 187 | } | ||
| > | 188 | } | ||
| > | 189 | |||
| > | 190 | /** | ||
| > | 191 | Destructor for use with the hashtable API. Frees | ||
| > | 192 | whgc_gc_entry objects by calling the assigned | ||
| > | 193 | dtors and then calling free(e). | ||
| > | 194 | |||
| > | 195 | The caller is expected to relink e's neighbors himself if needed | ||
| > | 196 | before calling this function. | ||
| > | 197 | |||
| > | 198 | The cx parameter is only used for firing a | ||
| > | 199 | whgc_event_destructing_item event. | ||
| > | 200 | */ | ||
| > | 201 | static void whgc_free_gc_entry( whgc_context * cx, | ||
| > | 202 | whgc_gc_entry * e ) | ||
| > | 203 | { | ||
| > | 204 | if( ! e ) return; | ||
| > | 205 | if( cx ) whgc_fire_event( cx, whgc_events.destructing_item, e->key, e->value ); | ||
| > | 206 | //MARKER;printf("Freeing GC item e[@%p]: key=[%p(@%p)] val[%p(@%p)]]\n",e,e->keyDtor,e->key,e->valueDtor,e->value); | ||
| > | 207 | if( e->valueDtor ) | ||
| > | 208 | { | ||
| > | 209 | //MARKER;printf("dtor'ing GC value %p( @%p )\n",e->valueDtor, e->value); | ||
| > | 210 | e->valueDtor(e->value); | ||
| > | 211 | } | ||
| > | 212 | if( e->keyDtor ) | ||
| > | 213 | { | ||
| > | 214 | //MARKER;printf("dtor'ing GC key %p( @%p )\n",e->keyDtor, e->key); | ||
| > | 215 | e->keyDtor(e->key); | ||
| > | 216 | } | ||
| > | 217 | if( cx ) cx->stats.alloced -= sizeof(whgc_gc_entry); | ||
| > | 218 | whgc_free(e); | ||
| > | 219 | } | ||
| > | 220 | |||
| > | 221 | /** | ||
| > | 222 | A no-op "destructor" to assist in tracking down destructions. | ||
| > | 223 | */ | ||
| > | 224 | static void whgc_free_noop( void * ARG_UNUSED(v) ) | ||
| > | 225 | { | ||
| > | 226 | //MARKER;printf("dtor no-op @%p\n",v); | ||
| > | 227 | } | ||
| > | 228 | |||
| > | 229 | static whhash_table * whgc_hashtable( whgc_context * cx ) | ||
| > | 230 | { | ||
| > | 231 | if( ! cx ) return 0; | ||
| > | 232 | if( cx->ht ) return cx->ht; | ||
| > | 233 | if( (cx->ht = whhash_create( 10, whhash_hash_void_ptr, whhash_cmp_void_ptr ) ) ) | ||
| > | 234 | { | ||
| > | 235 | whhash_set_dtors( cx->ht, whgc_free_noop, whgc_free_noop ); | ||
| > | 236 | /** | ||
| > | 237 | We use no-op dtors so we can log the destruction process, but cx->ht | ||
| > | 238 | does not own anything. Because we need predictable destruction order, | ||
| > | 239 | we manage a list of entries and destroy them in reverse order. | ||
| > | 240 | */ | ||
| > | 241 | /* Reminder: we don't update cx->stats.alloced here. We accommodate the | ||
| > | 242 | Hashtable size in whgc_get_stats(). | ||
| > | 243 | */ | ||
| > | 244 | } | ||
| > | 245 | return cx->ht; | ||
| > | 246 | } | ||
| > | 247 | |||
| > | 248 | whgc_context * whgc_create_context( void const * clientContext ) | ||
| > | 249 | { | ||
| > | 250 | whgc_context * cx = (whgc_context *)malloc(sizeof(whgc_context)); | ||
| > | 251 | if( ! cx ) return 0; | ||
| > | 252 | *cx = whgc_context_init; | ||
| > | 253 | cx->stats.alloced += sizeof(whgc_context); | ||
| > | 254 | cx->client = clientContext; | ||
| > | 255 | return cx; | ||
| > | 256 | } | ||
| > | 257 | |||
| > | 258 | bool whgc_add_listener( whgc_context *cx, whgc_listener_f f ) | ||
| > | 259 | { | ||
| > | 260 | if( ! cx || !f ) return false; | ||
| > | 261 | //MARKER;printf("Adding listener @%p() to cx @%p\n",f,cx); | ||
| > | 262 | whgc_listener * l = (whgc_listener *) | ||
| > | 263 | whgc_alloc( cx, sizeof(whgc_listener), whgc_free_noop ); | ||
| > | 264 | //malloc(sizeof(whgc_listener)); | ||
| > | 265 | if( ! l ) return 0; | ||
| > | 266 | *l = whgc_listener_init; | ||
| > | 267 | l->func = f; | ||
| > | 268 | //whgc_add( cx, l, whgc_free_noop ); | ||
| > | 269 | whgc_listener * L = cx->listeners; | ||
| > | 270 | if( L ) | ||
| > | 271 | { | ||
| > | 272 | while( L->next ) L = L->next; | ||
| > | 273 | L->next = l; | ||
| > | 274 | } | ||
| > | 275 | else | ||
| > | 276 | { | ||
| > | 277 | cx->listeners = l; | ||
| > | 278 | } | ||
| > | 279 | return true; | ||
| > | 280 | } | ||
| > | 281 | |||
| > | 282 | bool whgc_register( whgc_context * cx, | ||
| > | 283 | void * key, whgc_dtor_f keyDtor, | ||
| > | 284 | void * value, whgc_dtor_f valDtor ) | ||
| > | 285 | { | ||
| > | 286 | if( !key || !whgc_hashtable(cx) || (0 != whhash_search(cx->ht, key)) ) | ||
| > | 287 | { | ||
| > | 288 | return false; | ||
| > | 289 | } | ||
| > | 290 | whgc_gc_entry * e = (whgc_gc_entry*)whgc_alloc( 0, sizeof(whgc_gc_entry), 0 ); | ||
| > | 291 | if( ! e ) return false; | ||
| > | 292 | cx->stats.alloced += sizeof(whgc_gc_entry); | ||
| > | 293 | *e = whgc_gc_entry_init; | ||
| > | 294 | e->key = key; | ||
| > | 295 | e->keyDtor = keyDtor; | ||
| > | 296 | e->value = value; | ||
| > | 297 | e->valueDtor = valDtor; | ||
| > | 298 | //MARKER;printf("Registering GC item e[@%p]: key[@%p]/%p() = val[@%p]/%p()\n",e,e->key,e->keyDtor,e->value,e->valueDtor); | ||
| > | 299 | whhash_insert( cx->ht, key, e ); | ||
| > | 300 | ++(cx->stats.reg_count); | ||
| > | 301 | cx->stats.entry_count = whhash_count(cx->ht); | ||
| > | 302 | if( cx->current ) | ||
| > | 303 | { | ||
| > | 304 | e->left = cx->current; | ||
| > | 305 | cx->current->right = e; | ||
| > | 306 | } | ||
| > | 307 | cx->current = e; | ||
| > | 308 | whgc_fire_event( cx, whgc_events.registered, e->key, e->value ); | ||
| > | 309 | return true; | ||
| > | 310 | } | ||
| > | 311 | |||
| > | 312 | bool whgc_add( whgc_context * cx, void * key, whgc_dtor_f keyDtor ) | ||
| > | 313 | { | ||
| > | 314 | return whgc_register( cx, key, keyDtor, key, 0 ); | ||
| > | 315 | } | ||
| > | 316 | |||
| > | 317 | void * whgc_unregister( whgc_context * cx, void * key ) | ||
| > | 318 | { | ||
| > | 319 | if( ! cx || !cx->ht || !key ) return 0; | ||
| > | 320 | whgc_gc_entry * e = (whgc_gc_entry*)whhash_take( cx->ht, key ); | ||
| > | 321 | void * ret = e ? e->value : 0; | ||
| > | 322 | if( e ) | ||
| > | 323 | { | ||
| > | 324 | cx->stats.entry_count = whhash_count(cx->ht); | ||
| > | 325 | ++(cx->stats.unreg_count); | ||
| > | 326 | if( e->left ) e->left->right = e->right; | ||
| > | 327 | if( e->right ) e->right->left = e->left; | ||
| > | 328 | if( cx->current == e ) cx->current = (e->right ? e->right : e->left); | ||
| > | 329 | /** | ||
| > | 330 | ^^^ this is pedantic. In theory cx->current must always be | ||
| > | 331 | the right-most entry, so we could do: cx->current=e->left; | ||
| > | 332 | */ | ||
| > | 333 | void * k = e->key; | ||
| > | 334 | void * v = e->value; | ||
| > | 335 | whgc_free(e); | ||
| > | 336 | cx->stats.alloced -= sizeof(whgc_gc_entry); | ||
| > | 337 | whgc_fire_event( cx, whgc_events.unregistered, k, v ); | ||
| > | 338 | } | ||
| > | 339 | return ret; | ||
| > | 340 | } | ||
| > | 341 | |||
| > | 342 | void * whgc_search( whgc_context const * cx, void const * key ) | ||
| > | 343 | { | ||
| > | 344 | if( ! cx || !key || !cx->ht ) return 0; | ||
| > | 345 | whgc_gc_entry * e = (whgc_gc_entry*)whhash_search( cx->ht, key ); | ||
| > | 346 | return e ? e->value : 0; | ||
| > | 347 | } | ||
| > | 348 | |||
| > | 349 | void whgc_clear_context( whgc_context * cx ) | ||
| > | 350 | { | ||
| > | 351 | if( ! cx ) return; | ||
| > | 352 | //MARKER;printf("Cleaning up %u GC entries...\n",cx->stats.entry_count); | ||
| > | 353 | if( cx->ht ) | ||
| > | 354 | { | ||
| > | 355 | //MARKER;printf("Cleaning up %u GC entries...\n",whhash_count(cx->ht)); | ||
| > | 356 | whhash_clear( cx->ht ); | ||
| > | 357 | } | ||
| > | 358 | /** | ||
| > | 359 | Destroy registered entries in reverse order of | ||
| > | 360 | their registration. | ||
| > | 361 | */ | ||
| > | 362 | whgc_gc_entry * e = cx->current; | ||
| > | 363 | while( e ) | ||
| > | 364 | { | ||
| > | 365 | whgc_fire_event( cx, whgc_events.unregistered, e->key, e->value ); /* a bit of a kludge, really. */ | ||
| > | 366 | //MARKER;printf("Want to clean up @%p\n",e); | ||
| > | 367 | whgc_gc_entry * left = e->left; | ||
| > | 368 | whgc_free_gc_entry(cx,e); | ||
| > | 369 | e = left; | ||
| > | 370 | } | ||
| > | 371 | cx->stats.entry_count = 0; | ||
| > | 372 | cx->current = 0; | ||
| > | 373 | } | ||
| > | 374 | void whgc_destroy_context( whgc_context * cx ) | ||
| > | 375 | { | ||
| > | 376 | if( ! cx ) return; | ||
| > | 377 | #if 0 | ||
| > | 378 | { | ||
| > | 379 | whhash_stats s = whhash_get_stats( cx->ht ); | ||
| > | 380 | MARKER;printf("GC context search collisions: %u\n",s.search_collisions); | ||
| > | 381 | } | ||
| > | 382 | #endif | ||
| > | 383 | whgc_fire_event( cx, whgc_events.destructing_context, 0, 0 ); | ||
| > | 384 | whgc_clear_context( cx ); | ||
| > | 385 | if( cx->ht ) | ||
| > | 386 | { | ||
| > | 387 | whhash_destroy( cx->ht ); | ||
| > | 388 | cx->ht = 0; | ||
| > | 389 | } | ||
| > | 390 | |||
| > | 391 | cx->client = 0; /* needs to stay valid until all events are fires, in case clients use it as a lookup key (i do) */ | ||
| > | 392 | whgc_listener * L = cx->listeners; | ||
| > | 393 | cx->listeners = 0; | ||
| > | 394 | while( L ) | ||
| > | 395 | { | ||
| > | 396 | whgc_listener * l = L->next; | ||
| > | 397 | cx->stats.alloced -= sizeof(whgc_listener); | ||
| > | 398 | whgc_free(L); | ||
| > | 399 | L = l; | ||
| > | 400 | } | ||
| > | 401 | whgc_free(cx); | ||
| > | 402 | } | ||
| > | 403 | |||
| > | 404 | void whgc_context_dtor( void * p ) | ||
| > | 405 | { | ||
| > | 406 | whgc_destroy_context( (whgc_context*)p ); | ||
| > | 407 | } | ||
| > | 408 | |||
| > | 409 | whgc_stats whgc_get_stats( whgc_context const * cx ) | ||
| > | 410 | { | ||
| > | 411 | whgc_stats s = cx ? cx->stats : whgc_stats_init; | ||
| > | 412 | if( ! cx ) return s; | ||
| > | 413 | whhash_stats const hs = whhash_get_stats( cx->ht ); | ||
| > | 414 | s.alloced += hs.alloced; | ||
| > | 415 | return s; | ||
| > | 416 | } | ||
| > | 417 | |||
| > | 418 | #if defined(__cplusplus) | ||
| > | 419 | } /* extern "C" */ | ||
| > | 420 | #endif | ||
Added whgc.h
| Old () | New (78d0261b2af5fcdd) | |||
|---|---|---|---|---|
| > | 1 | #ifndef WANDERINGHORSE_NET_WHGC_H_INCLUDED | ||
| > | 2 | #define WANDERINGHORSE_NET_WHGC_H_INCLUDED | ||
| > | 3 | |||
| > | 4 | #include <stdarg.h> | ||
| > | 5 | #include <stddef.h> | ||
| > | 6 | #ifdef __cplusplus | ||
| > | 7 | extern "C" { | ||
| > | 8 | #endif | ||
| > | 9 | |||
| > | 10 | #ifndef __cplusplus | ||
| > | 11 | # ifndef WHGC_HAVE_STDBOOL | ||
| > | 12 | # define WHGC_HAVE_STDBOOL 0 | ||
| > | 13 | # endif | ||
| > | 14 | |||
| > | 15 | # if defined(WHGC_HAVE_STDBOOL) && !(WHGC_HAVE_STDBOOL) | ||
| > | 16 | # if !defined(bool) | ||
| > | 17 | # define bool char | ||
| > | 18 | # endif | ||
| > | 19 | # if !defined(true) | ||
| > | 20 | # define true 1 | ||
| > | 21 | # endif | ||
| > | 22 | # if !defined(false) | ||
| > | 23 | # define false 0 | ||
| > | 24 | # endif | ||
| > | 25 | # else /* aha! stdbool.h! C99. */ | ||
| > | 26 | # include <stdbool.h> | ||
| > | 27 | # endif /* WHGC_HAVE_STDBOOL */ | ||
| > | 28 | #endif /* __cplusplus */ | ||
| > | 29 | |||
| > | 30 | /*! | ||
| > | 31 | @page whgc_page_main whgc: Garbage Collection lib for C | ||
| > | 32 | |||
| > | 33 | @section whgc_sec_about_whgc About whgc | ||
| > | 34 | |||
| > | 35 | whgc (the WanderingHorse.net Garbage Collector) is a simple garbage | ||
| > | 36 | collector (GC) for C. | ||
| > | 37 | |||
| > | 38 | Author: Stephan Beal (http://wanderinghorse.net/home/stephan) | ||
| > | 39 | |||
| > | 40 | License: the core library is Public Domain, but some of the | ||
| > | 41 | borrowed utility code is released under a BSD license (see | ||
| > | 42 | whhash.{c,h} for details). | ||
| > | 43 | |||
| > | 44 | Home page: whgc is part of the pegc project: | ||
| > | 45 | http://fossil.wanderinghorse.net/repos/pegc/ | ||
| > | 46 | |||
| > | 47 | whgc is a small garbage collection library for C. The library | ||
| > | 48 | provides context objects to which one can attach arbitrary | ||
| > | 49 | data (via void pointers) and arbitrary destructor functions. | ||
| > | 50 | When a GC context is destroyed, the destructors are called | ||
| > | 51 | for each attached item. | ||
| > | 52 | |||
| > | 53 | The original use case for whgc was a parser toolkit which | ||
| > | 54 | needed to allocate dynamic resources while generating a | ||
| > | 55 | grammar. By attaching the dynamic data to the parser's GC, we | ||
| > | 56 | eliminated a number of headaches involved with ownership of the | ||
| > | 57 | dynamic data. It also simplified many lookup operations when we | ||
| > | 58 | needed to find shared data. | ||
| > | 59 | |||
| > | 60 | @section set_features Features and Misfeatures | ||
| > | 61 | |||
| > | 62 | The main feature of whgc are: | ||
| > | 63 | |||
| > | 64 | - It allows one to bind key/value pairs to a context | ||
| > | 65 | object. When that object is destroyed, client-defined | ||
| > | 66 | destructor functions will be called on for all of the keys and | ||
| > | 67 | values. This greatly simplifies management and ownership of | ||
| > | 68 | dynamically allocated memory for some use cases. | ||
| > | 69 | |||
| > | 70 | - It supports event listeners, to assist in debugging the | ||
| > | 71 | lifetimes of GC'd objects. | ||
| > | 72 | |||
| > | 73 | - Aside from GC, it's sometimes useful as a general-purposes lookup | ||
| > | 74 | table (a hashtable of (key=void *,value=void *)). | ||
| > | 75 | |||
| > | 76 | @section whgc_sec_example Example | ||
| > | 77 | |||
| > | 78 | An exceedingly simple example of using it: | ||
| > | 79 | |||
| > | 80 | @code | ||
| > | 81 | whgc_context * cx = whgc_create_context( 0 ); | ||
| > | 82 | if( ! cx ) { ... error, probably OOM ... } | ||
| > | 83 | |||
| > | 84 | int * i = (int*)malloc(sizeof(int)); | ||
| > | 85 | *i = 42; | ||
| > | 86 | whgc_add( i, free ); | ||
| > | 87 | |||
| > | 88 | struct mystruct * my = (struct mystruct*)malloc(sizeof(struct mystruct)); | ||
| > | 89 | my->foo = "hi"; | ||
| > | 90 | my->bar = 42.42; | ||
| > | 91 | // Assume this function exists: | ||
| > | 92 | // void mystruct_dtor(void*); | ||
| > | 93 | // and that it deallocates mystruct objects. | ||
| > | 94 | whgc_add( cx, my, mystruct_dtor ); | ||
| > | 95 | |||
| > | 96 | ... | ||
| > | 97 | |||
| > | 98 | whgc_destroy_context( cx ); | ||
| > | 99 | // now all items added to the context are destroyed using the | ||
| > | 100 | // given destructor callbacks. They are destroyed in the reverse | ||
| > | 101 | // order they were added (LIFO). | ||
| > | 102 | @endcode | ||
| > | 103 | |||
| > | 104 | Aside from whgc_add(), there is the more flexible | ||
| > | 105 | whgc_register(), which allows one to register key/value pairs, | ||
| > | 106 | and separate destructors for each key and value. The lookup key | ||
| > | 107 | can be used with whgc_search() to find the mapped data. | ||
| > | 108 | |||
| > | 109 | Assigning a lookup key to data is often useful when we have | ||
| > | 110 | some common handle type we're passing around but need to | ||
| > | 111 | associated dynamically-allocated objects to them. The GC approach | ||
| > | 112 | allows us to transfer ownership to the GC context, so that we can | ||
| > | 113 | map arbitrary data to an arbitrary object and not have to worry | ||
| > | 114 | about whether or not that memory will be deallocated later. | ||
| > | 115 | |||
| > | 116 | @section whgc_sec_threadsafety Thread safety | ||
| > | 117 | |||
| > | 118 | By default it is never legal to use the same context from | ||
| > | 119 | multiple threads at the same time. That said, the client may | ||
| > | 120 | use their own locking to serialize access. All API functions | ||
| > | 121 | which take a (whgc_context const *) argument require only a | ||
| > | 122 | read lock, whereas those taking a (whgc_context*) argument | ||
| > | 123 | require a exclusive (read/write) access. whgc_create_context() | ||
| > | 124 | is reentrant and does not need to be locked. | ||
| > | 125 | */ | ||
| > | 126 | |||
| > | 127 | /** @struct whgc_context | ||
| > | 128 | |||
| > | 129 | whgc_context is an opaque handle for use with the whgc_xxx() | ||
| > | 130 | routines. They are created using whgc_create_context() and | ||
| > | 131 | destroyed using whgc_destroy_context(). | ||
| > | 132 | */ | ||
| > | 133 | struct whgc_context; | ||
| > | 134 | typedef struct whgc_context whgc_context; | ||
| > | 135 | |||
| > | 136 | /** @typedef void (*whgc_dtor_f)(void*) | ||
| > | 137 | |||
| > | 138 | Typedef for deallocation functions symantically compatible with | ||
| > | 139 | free(). | ||
| > | 140 | */ | ||
| > | 141 | typedef void (*whgc_dtor_f)(void*); | ||
| > | 142 | |||
| > | 143 | /** | ||
| > | 144 | If cx is null this function works just like malloc() except: | ||
| > | 145 | |||
| > | 146 | - It calls memset() to zero out the memory. | ||
| > | 147 | |||
| > | 148 | - If cx is not null then ownership of the memory transfers is | ||
| > | 149 | transfered to cx calling whgc_add(cx,theMemory,dtor). cx's memory | ||
| > | 150 | memory allocation telemetry (see whgc_get_stats()) is also updated. | ||
| > | 151 | |||
| > | 152 | - If the cx is null then the caller owns the returned memory and must free | ||
| > | 153 | it by passing it to free(). | ||
| > | 154 | |||
| > | 155 | It returns 0 on an alloc error or if size is 0. | ||
| > | 156 | */ | ||
| > | 157 | void * whgc_alloc( whgc_context * cx, size_t size, whgc_dtor_f dtor ); | ||
| > | 158 | |||
| > | 159 | /** | ||
| > | 160 | Creates a gc context. The clientContext point may internally be | ||
| > | 161 | used as a lookup key or some such but is otherwise unused by | ||
| > | 162 | this API. | ||
| > | 163 | */ | ||
| > | 164 | whgc_context * whgc_create_context( void const * clientContext ); | ||
| > | 165 | |||
| > | 166 | /** | ||
| > | 167 | Returns the client-supplied pointer which was passed to | ||
| > | 168 | whgc_create_context() for the given context. It is sometimes | ||
| > | 169 | useful as a lookup key. | ||
| > | 170 | */ | ||
| > | 171 | void const * whgc_get_context_client(whgc_context const *); | ||
| > | 172 | |||
| > | 173 | |||
| > | 174 | /** | ||
| > | 175 | Registers an arbitrary key and value with the given garbage | ||
| > | 176 | collector, such that whgc_destroy_context(st) will clean up the | ||
| > | 177 | resources using the given destructor functions (which may be 0, | ||
| > | 178 | meaning "do not destroy"). | ||
| > | 179 | |||
| > | 180 | A custom hash function is supplied which hashes the address of | ||
| > | 181 | the key (that is, a hash value of the numeric value of the | ||
| > | 182 | pointer address). Thus for two keys to be equal they must have | ||
| > | 183 | the same address (or must have found a collision in the hash | ||
| > | 184 | algorithm). | ||
| > | 185 | |||
| > | 186 | If keyDtor is not 0 then during cleanup keyDtor(key) is | ||
| > | 187 | called. Likewise, if valDtor is not 0 then valDtor(value) is | ||
| > | 188 | called at cleanup time. | ||
| > | 189 | |||
| > | 190 | It is perfectly legal for the key and value to be the same | ||
| > | 191 | object, but only if at least one of the destructor functions is | ||
| > | 192 | 0 or a no-op function (otherwise a double-free will happen). | ||
| > | 193 | |||
| > | 194 | It is legal for both keyDtor and valDtor to be 0, in which case | ||
| > | 195 | this API simply holds a reference to the data but will not destroy | ||
| > | 196 | it. | ||
| > | 197 | |||
| > | 198 | Returns true if the item is now registered, or false on error | ||
| > | 199 | (!st, !key, key was already mapped, or a memory allocation error). | ||
| > | 200 | |||
| > | 201 | It is illegal to register the same key more than once with the | ||
| > | 202 | same context. Doing so will cause false to be returned. | ||
| > | 203 | |||
| > | 204 | Note that the destruction order of items cleaned up using this | ||
| > | 205 | mechanism is in reverse order of their registration. | ||
| > | 206 | */ | ||
| > | 207 | bool whgc_register( whgc_context * cx, | ||
| > | 208 | void * key, whgc_dtor_f keyDtor, | ||
| > | 209 | void * value, whgc_dtor_f valDtor ); | ||
| > | 210 | |||
| > | 211 | /** | ||
| > | 212 | Convenience form of whgc_register(cx,key,keyDtor,key,0). | ||
| > | 213 | */ | ||
| > | 214 | bool whgc_add( whgc_context *cx, void * key, whgc_dtor_f keyDtor ); | ||
| > | 215 | |||
| > | 216 | /** | ||
| > | 217 | Removes the given key from the given context, transfering ownership | ||
| > | 218 | of the key and the associated value to the caller. | ||
| > | 219 | */ | ||
| > | 220 | void * whgc_unregister( whgc_context * cx, void * key ); | ||
| > | 221 | |||
| > | 222 | /** | ||
| > | 223 | Frees all resources associated with the given context. | ||
| > | 224 | All entries which have been added via whgc_add() are passed to | ||
| > | 225 | the dtor function which was assigned to them via whgc_add(). | ||
| > | 226 | |||
| > | 227 | Note that the destruction order is in reverse order of the | ||
| > | 228 | registration (FIFO). | ||
| > | 229 | */ | ||
| > | 230 | void whgc_destroy_context( whgc_context * ); | ||
| > | 231 | |||
| > | 232 | /** | ||
| > | 233 | Clears all gc entries, calling their associated destructors. | ||
| > | 234 | |||
| > | 235 | This does not destroy cx, only its entries. | ||
| > | 236 | */ | ||
| > | 237 | void whgc_clear_context( whgc_context * cx ); | ||
| > | 238 | |||
| > | 239 | /** | ||
| > | 240 | A destructor for use with functions taking a whgc_dtor_f parameter. | ||
| > | 241 | It requires that its argument be a whgc_context pointer, on which | ||
| > | 242 | it calls whgc_destroy_context(). | ||
| > | 243 | |||
| > | 244 | This can be useful if you want one gc context to manage multiple | ||
| > | 245 | other contexts. Simply use, e.g.: | ||
| > | 246 | |||
| > | 247 | @code | ||
| > | 248 | whgc_gc_add( mainCtx, subCtx, whgc_context_dtor ); | ||
| > | 249 | @endcode | ||
| > | 250 | |||
| > | 251 | where mainCtx is the primary context and subCtx is a context which | ||
| > | 252 | belongs to mainCtx. | ||
| > | 253 | */ | ||
| > | 254 | void whgc_context_dtor( void * ); | ||
| > | 255 | |||
| > | 256 | /** | ||
| > | 257 | Searches the given context for the given key. Returns 0 if the | ||
| > | 258 | key is not found. Ownership of the returned objet is not changed. | ||
| > | 259 | */ | ||
| > | 260 | void * whgc_search( whgc_context const * cx, void const * key ); | ||
| > | 261 | |||
| > | 262 | /** | ||
| > | 263 | A type for storing some telemetry for a whgc_context. | ||
| > | 264 | Use whgc_get_stats() to collect the current stats of | ||
| > | 265 | a parser. | ||
| > | 266 | */ | ||
| > | 267 | struct whgc_stats | ||
| > | 268 | { | ||
| > | 269 | /** | ||
| > | 270 | Number of entries in the context. | ||
| > | 271 | */ | ||
| > | 272 | size_t entry_count; | ||
| > | 273 | /** | ||
| > | 274 | Number of registrations made in the context. | ||
| > | 275 | */ | ||
| > | 276 | size_t reg_count; | ||
| > | 277 | /** | ||
| > | 278 | Number of items unregistered from the context. | ||
| > | 279 | */ | ||
| > | 280 | size_t unreg_count; | ||
| > | 281 | /** | ||
| > | 282 | A rough *approximatation* of amount of memory allocated for | ||
| > | 283 | whgc-specific internal structures used by the context, | ||
| > | 284 | including the size of the underlying hashtable(s). A | ||
| > | 285 | context has no way of knowing how much memory is used by | ||
| > | 286 | registered items. | ||
| > | 287 | |||
| > | 288 | Don't take this number too seriously. i try to keep the | ||
| > | 289 | telemetry up to date for allocs, but keeping track of | ||
| > | 290 | deallocs is more difficult due to timing and scope issues. | ||
| > | 291 | */ | ||
| > | 292 | size_t alloced; | ||
| > | 293 | }; | ||
| > | 294 | typedef struct whgc_stats whgc_stats; | ||
| > | 295 | |||
| > | 296 | /** | ||
| > | 297 | Returns the current stats for the given context. | ||
| > | 298 | */ | ||
| > | 299 | whgc_stats whgc_get_stats( whgc_context const * ); | ||
| > | 300 | |||
| > | 301 | /** | ||
| > | 302 | The type is not intended to be instantiated directly by | ||
| > | 303 | clients, but instead used via the whgc_events shared object. | ||
| > | 304 | |||
| > | 305 | A helper type for simulating scoped named enums in C. The | ||
| > | 306 | values in this type correspond to those used by whgc_event, | ||
| > | 307 | which allows event listeners to distinguish between various | ||
| > | 308 | event types in one handler. | ||
| > | 309 | |||
| > | 310 | All members of this type must have unique values, but the | ||
| > | 311 | values used by the whgc_events object are unspecified (other | ||
| > | 312 | than that they are each unique). | ||
| > | 313 | */ | ||
| > | 314 | struct whgc_events_t { | ||
| > | 315 | /** | ||
| > | 316 | Signal that a GC item has been registered. | ||
| > | 317 | */ | ||
| > | 318 | short registered; | ||
| > | 319 | |||
| > | 320 | /** | ||
| > | 321 | Signal that a GC item has been unregistered. | ||
| > | 322 | */ | ||
| > | 323 | short unregistered; | ||
| > | 324 | |||
| > | 325 | /** | ||
| > | 326 | Signal that a key/val pair is about to e passed through the | ||
| > | 327 | item's dtor functions. This event is triggered even if the dtor | ||
| > | 328 | functions won't actually be called (e.g. they are 0). Thus this | ||
| > | 329 | signals a "virtual" destruction. | ||
| > | 330 | */ | ||
| > | 331 | short destructing_item; | ||
| > | 332 | |||
| > | 333 | /** | ||
| > | 334 | Signal that a context is about to be destroyed. | ||
| > | 335 | */ | ||
| > | 336 | short destructing_context; | ||
| > | 337 | |||
| > | 338 | /** | ||
| > | 339 | This marks the highest reserved event ID. In theory, values | ||
| > | 340 | above that can be used by client code, but i can personally | ||
| > | 341 | see no use for doing so. Note that the IDs in this class | ||
| > | 342 | are not guaranteed to be sequential, so don't use this as | ||
| > | 343 | an end condition for a loop. | ||
| > | 344 | */ | ||
| > | 345 | short last_event_id; | ||
| > | 346 | }; | ||
| > | 347 | typedef struct whgc_events_t whgc_events_t; | ||
| > | 348 | /** @var const whgc_events_t whgc_events | ||
| > | 349 | |||
| > | 350 | This is a shared instance of whgc_events_t which holds event | ||
| > | 351 | type IDs. It is used instead of an enum because i find it | ||
| > | 352 | prettier. Sample usage, from a whgc_listener_f implementation: | ||
| > | 353 | |||
| > | 354 | @code | ||
| > | 355 | void my_gc_listener( whgc_event const * event ) | ||
| > | 356 | { | ||
| > | 357 | if( whgc_events.destructing_context == event->type ) | ||
| > | 358 | { | ||
| > | 359 | whgc_stats const st = whgc_get_stats( event->cx ); | ||
| > | 360 | printf("Approx memory allocated by gc context: %u\n", st.alloced); | ||
| > | 361 | } | ||
| > | 362 | } | ||
| > | 363 | @endcode | ||
| > | 364 | */ | ||
| > | 365 | extern const whgc_events_t whgc_events; | ||
| > | 366 | |||
| > | 367 | /** | ||
| > | 368 | whgc_event is a type for sending information regarding certain GC | ||
| > | 369 | context state changes to registered listeners. | ||
| > | 370 | */ | ||
| > | 371 | struct whgc_event | ||
| > | 372 | { | ||
| > | 373 | /** | ||
| > | 374 | The context from which this event originated. This parameter | ||
| > | 375 | is set for all event types. | ||
| > | 376 | */ | ||
| > | 377 | whgc_context const * cx; | ||
| > | 378 | |||
| > | 379 | /** | ||
| > | 380 | Specifies the logical type of the event, which also determines | ||
| > | 381 | the semantics of the event. | ||
| > | 382 | |||
| > | 383 | The type IDs are defined in the whgc_events shared object | ||
| > | 384 | and alert the user about which members of the event are set | ||
| > | 385 | and. e.g. whgc_events.destructing_context does not set the | ||
| > | 386 | key/value members because they are meaningless for that | ||
| > | 387 | event. The semantics of the various event types are: | ||
| > | 388 | |||
| > | 389 | - whgc_events.registered signals that the key/value members have | ||
| > | 390 | just been registered with the context. | ||
| > | 391 | |||
| > | 392 | - whgc_events.unregistered signals that the key/value members | ||
| > | 393 | have just been unregistered from the context. | ||
| > | 394 | |||
| > | 395 | - whgc_events.destructing_item signals that the key/value | ||
| > | 396 | members are about to be passed to their registered dtor | ||
| > | 397 | functions (if any). | ||
| > | 398 | |||
| > | 399 | - whgc_events.destructing_context signals that the cx | ||
| > | 400 | member is about to be destroyed (key/value will be 0). | ||
| > | 401 | |||
| > | 402 | These semantics should be respected by whgc_listener_f | ||
| > | 403 | implementations. | ||
| > | 404 | */ | ||
| > | 405 | short type; | ||
| > | 406 | |||
| > | 407 | /** | ||
| > | 408 | The key associated with the event. Only set for events of type: | ||
| > | 409 | |||
| > | 410 | whgc_events.registered, whgc_events.unregistered, whgc_events.destructing_item | ||
| > | 411 | |||
| > | 412 | */ | ||
| > | 413 | void const * key; | ||
| > | 414 | /** | ||
| > | 415 | The value associated with the event. Only set for events of type: | ||
| > | 416 | |||
| > | 417 | whgc_events.registered, whgc_events.unregistered, whgc_events.destructing_item | ||
| > | 418 | |||
| > | 419 | */ | ||
| > | 420 | void const * value; | ||
| > | 421 | }; | ||
| > | 422 | typedef struct whgc_event whgc_event; | ||
| > | 423 | /** @typedef void (*whgc_listener_f)( whgc_event const * ev ) | ||
| > | 424 | |||
| > | 425 | A typedef for whgc context event listers. The primary use case | ||
| > | 426 | of a listener is to help debug the lifetimes of GC'd items. The | ||
| > | 427 | listener is guaranteed to not get a null pointer, so long as it | ||
| > | 428 | is triggered from inside of this API. | ||
| > | 429 | */ | ||
| > | 430 | typedef void (*whgc_listener_f)( whgc_event const * ev ); | ||
| > | 431 | |||
| > | 432 | /** | ||
| > | 433 | Adds an event listener to the context. The listener is called | ||
| > | 434 | when certain events happen within the given context. The call | ||
| > | 435 | order of multiple listeners is undefined. There is no way to | ||
| > | 436 | remove a listener. | ||
| > | 437 | |||
| > | 438 | Listeners are best reserved for debugging purposes only, as | ||
| > | 439 | they apply some overhead to all add/remove operations. The | ||
| > | 440 | exact amount of overhead is largely a function of what the | ||
| > | 441 | listeners do, as the context is effectively blocked until the | ||
| > | 442 | listeners return. | ||
| > | 443 | */ | ||
| > | 444 | bool whgc_add_listener( whgc_context *, whgc_listener_f f ); | ||
| > | 445 | |||
| > | 446 | #ifdef __cplusplus | ||
| > | 447 | } /* extern "C" */ | ||
| > | 448 | #endif | ||
| > | 449 | |||
| > | 450 | |||
| > | 451 | #endif /* WANDERINGHORSE_NET_WHGC_H_INCLUDED */ | ||
Added whhash.c
| Old () | New (6a4c505dee2c12e5) | |||
|---|---|---|---|---|
| > | 1 | /* Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ | ||
| > | 2 | /* Copyright (C) 2008 Stephan Beal (http://wanderinghorse.net/home/stephan/) */ | ||
| > | 3 | |||
| > | 4 | #include "whhash.h" | ||
| > | 5 | #include <stdlib.h> | ||
| > | 6 | //#include <stdio.h> | ||
| > | 7 | #include <string.h> | ||
| > | 8 | |||
| > | 9 | #ifdef __cplusplus | ||
| > | 10 | extern "C" { | ||
| > | 11 | #endif | ||
| > | 12 | |||
| > | 13 | /* | ||
| > | 14 | Internal type for holding hashtable entries. | ||
| > | 15 | */ | ||
| > | 16 | struct whhash_entry | ||
| > | 17 | { | ||
| > | 18 | void *k, *v; | ||
| > | 19 | whhash_val_t h; | ||
| > | 20 | struct whhash_entry *next; | ||
| > | 21 | }; | ||
| > | 22 | typedef struct whhash_entry whhash_entry; | ||
| > | 23 | |||
| > | 24 | #define WHHASH_STATS_INIT {\ | ||
| > | 25 | 0,/*entries*/ \ | ||
| > | 26 | 0,/*insertions*/ \ | ||
| > | 27 | 0,/*removals*/ \ | ||
| > | 28 | 0,/*searches*/ \ | ||
| > | 29 | 0,/*alloced*/ \ | ||
| > | 30 | 0,/*expansions*/ \ | ||
| > | 31 | 0,/*search_collisions*/ \ | ||
| > | 32 | } | ||
| > | 33 | static const whhash_stats whhash_stats_init = WHHASH_STATS_INIT; | ||
| > | 34 | |||
| > | 35 | |||
| > | 36 | struct whhash_table { | ||
| > | 37 | size_t tablelength; | ||
| > | 38 | struct whhash_entry **table; | ||
| > | 39 | size_t entrycount; | ||
| > | 40 | size_t loadlimit; | ||
| > | 41 | size_t primeindex; | ||
| > | 42 | whhash_val_t (*hashfn) (void const *k); | ||
| > | 43 | int (*eqfn) (void const *k1, void const *k2); | ||
| > | 44 | void (*freeKey)( void * ); | ||
| > | 45 | void (*freeVal)( void * ); | ||
| > | 46 | unsigned int flags; | ||
| > | 47 | whhash_stats stats; | ||
| > | 48 | }; | ||
| > | 49 | static const whhash_table | ||
| > | 50 | whhash_init = { 0, /*tablelength*/ | ||
| > | 51 | 0, /* table */ | ||
| > | 52 | 0, /* entrycount */ | ||
| > | 53 | 0, /* loadlimit */ | ||
| > | 54 | 0, /* primeindex */ | ||
| > | 55 | 0, /* hashfn */ | ||
| > | 56 | 0, /* eqfn */ | ||
| > | 57 | 0, /* freeKey */ | ||
| > | 58 | 0, /* freeVal */ | ||
| > | 59 | 0, /* flags */ | ||
| > | 60 | WHHASH_STATS_INIT | ||
| > | 61 | }; | ||
| > | 62 | const whhash_val_t whhash_hash_val_err = (whhash_val_t)-1; | ||
| > | 63 | |||
| > | 64 | struct whhash_flags { | ||
| > | 65 | /** | ||
| > | 66 | Signals a hash for use with the whhash_st_xxx() functions. | ||
| > | 67 | Set only by whhash_sh_create(). | ||
| > | 68 | */ | ||
| > | 69 | const int SH_API; | ||
| > | 70 | } whhash_flags = { | ||
| > | 71 | 0x0001 /* SH_API */ | ||
| > | 72 | }; | ||
| > | 73 | |||
| > | 74 | /** | ||
| > | 75 | Returns h->hashfn(k), or whhash_hash_val_err if either h or k are 0. | ||
| > | 76 | */ | ||
| > | 77 | static whhash_val_t whhash_hash(whhash_table *h, void const *k) | ||
| > | 78 | { | ||
| > | 79 | if( !h || !k ) return whhash_hash_val_err; | ||
| > | 80 | /* Aim to protect against poor hash functions by adding logic here | ||
| > | 81 | * - logic taken from java 1.4 whhash_table source */ | ||
| > | 82 | whhash_val_t i = h->hashfn(k); | ||
| > | 83 | i += ~(i << 9); | ||
| > | 84 | i ^= ((i >> 14) | (i << 18)); /* >>> */ | ||
| > | 85 | i += (i << 4); | ||
| > | 86 | i ^= ((i >> 10) | (i << 22)); /* >>> */ | ||
| > | 87 | return i; | ||
| > | 88 | } | ||
| > | 89 | |||
| > | 90 | /*****************************************************************************/ | ||
| > | 91 | /* Returns (hashvalue % tablelength) */ | ||
| > | 92 | #if 0 | ||
| > | 93 | static size_t whhash_index(size_t tablelength, size_t hashvalue) | ||
| > | 94 | { | ||
| > | 95 | return (hashvalue % tablelength); | ||
| > | 96 | } | ||
| > | 97 | #else | ||
| > | 98 | #define whhash_index(LEN,HV) (HV % LEN) | ||
| > | 99 | #endif | ||
| > | 100 | |||
| > | 101 | /** | ||
| > | 102 | For internal use only: this func frees the memory associated with | ||
| > | 103 | key, using h->freeKey (if set). Results are undefined if key is | ||
| > | 104 | not a key in h and if key is used after this func is called. | ||
| > | 105 | */ | ||
| > | 106 | static void whhash_free_key(whhash_table const * h, void * key ) | ||
| > | 107 | { | ||
| > | 108 | if( h && key && h->freeKey ) h->freeKey( key ); | ||
| > | 109 | } | ||
| > | 110 | |||
| > | 111 | /** | ||
| > | 112 | Like whhash_free_key() but applies to the value. | ||
| > | 113 | */ | ||
| > | 114 | static void whhash_free_val(whhash_table const * h, void * val ) | ||
| > | 115 | { | ||
| > | 116 | if( h && val && h->freeVal ) h->freeVal( val ); | ||
| > | 117 | } | ||
| > | 118 | |||
| > | 119 | |||
| > | 120 | /* | ||
| > | 121 | Credit for primes table: Aaron Krowne | ||
| > | 122 | http://br.endernet.org/~akrowne/ | ||
| > | 123 | http://planetmath.org/encyclopedia/GoodHashTablePrimes.html | ||
| > | 124 | */ | ||
| > | 125 | static const whhash_val_t primes[] = { | ||
| > | 126 | #if 0 | ||
| > | 127 | 2,3,5,7,11,13, | ||
| > | 128 | #endif | ||
| > | 129 | #if 0 | ||
| > | 130 | 17,19,23,29,31, | ||
| > | 131 | #endif | ||
| > | 132 | #if 0 | ||
| > | 133 | 37,41,43,47, | ||
| > | 134 | #endif | ||
| > | 135 | #if 1 | ||
| > | 136 | 7,13,23, | ||
| > | 137 | #endif | ||
| > | 138 | 53, 97, 193, 389, | ||
| > | 139 | 769, 1543, 3079, 6151, | ||
| > | 140 | 12289, 24593, 49157, 98317, | ||
| > | 141 | 196613, 393241, 786433, 1572869, | ||
| > | 142 | 3145739, 6291469, 12582917, 25165843, | ||
| > | 143 | 50331653, 100663319, 201326611, 402653189, | ||
| > | 144 | 805306457, 1610612741 | ||
| > | 145 | }; | ||
| > | 146 | const whhash_val_t prime_table_length = sizeof(primes)/sizeof(primes[0]); | ||
| > | 147 | const float max_load_factor = | ||
| > | 148 | /* original developer's value was 0.65. */ | ||
| > | 149 | 0.70 | ||
| > | 150 | ; | ||
| > | 151 | |||
| > | 152 | |||
| > | 153 | /** | ||
| > | 154 | Custom implementation of ceil() to avoid a dependency on | ||
| > | 155 | libmath on some systems (e.g. the tcc compiler). | ||
| > | 156 | */ | ||
| > | 157 | static whhash_val_t whhash_ceil( double d ) | ||
| > | 158 | { | ||
| > | 159 | whhash_val_t x = (whhash_val_t)d; | ||
| > | 160 | return (( d - (double)x )>0.0) | ||
| > | 161 | ? (x+1) | ||
| > | 162 | : x; | ||
| > | 163 | } | ||
| > | 164 | |||
| > | 165 | /*****************************************************************************/ | ||
| > | 166 | whhash_table * | ||
| > | 167 | whhash_create(whhash_val_t minsize, | ||
| > | 168 | whhash_val_t (*hashf) (void const *), | ||
| > | 169 | int (*eqf) (void const *,void const *)) | ||
| > | 170 | { | ||
| > | 171 | whhash_table *h; | ||
| > | 172 | whhash_val_t pindex, size = primes[0]; | ||
| > | 173 | /* Check requested whhash_table isn't too large */ | ||
| > | 174 | if (minsize > (1u << 30)) return NULL; | ||
| > | 175 | /* Enforce size as prime */ | ||
| > | 176 | for (pindex=0; pindex < prime_table_length; pindex++) { | ||
| > | 177 | if (primes[pindex] > minsize) { size = primes[pindex]; break; } | ||
| > | 178 | } | ||
| > | 179 | h = (whhash_table *)malloc(sizeof(whhash_table)); | ||
| > | 180 | if (NULL == h) return NULL; /*oom*/ | ||
| > | 181 | h->stats.alloced = sizeof(whhash_table); | ||
| > | 182 | *h = whhash_init; | ||
| > | 183 | h->freeKey = 0; | ||
| > | 184 | h->freeVal = 0; | ||
| > | 185 | #if 0 | ||
| > | 186 | h->table = (whhash_entry **)malloc(sizeof(whhash_entry*) * size); | ||
| > | 187 | if(h->table) memset(h->table, 0, size * sizeof(whhash_entry *)); | ||
| > | 188 | #else | ||
| > | 189 | h->table = (whhash_entry **)calloc(size, sizeof(whhash_entry*)); | ||
| > | 190 | #endif | ||
| > | 191 | if (NULL == h->table) { free(h); return NULL; } /*oom*/ | ||
| > | 192 | h->stats.alloced += (sizeof(whhash_entry*) * size); | ||
| > | 193 | h->tablelength = size; | ||
| > | 194 | h->primeindex = pindex; | ||
| > | 195 | h->entrycount = 0; | ||
| > | 196 | h->hashfn = hashf; | ||
| > | 197 | h->eqfn = eqf; | ||
| > | 198 | h->loadlimit = whhash_ceil(size * max_load_factor); | ||
| > | 199 | return h; | ||
| > | 200 | } | ||
| > | 201 | /*****************************************************************************/ | ||
| > | 202 | |||
| > | 203 | |||
| > | 204 | |||
| > | 205 | static int | ||
| > | 206 | whhash_expand(whhash_table *h) | ||
| > | 207 | { | ||
| > | 208 | if( ! h ) return 0; | ||
| > | 209 | /* Jump up to the next size in the primes table to accomodate more entries */ | ||
| > | 210 | whhash_entry **newtable; | ||
| > | 211 | whhash_entry *e; | ||
| > | 212 | whhash_entry **pE; | ||
| > | 213 | whhash_val_t newsize, i, index; | ||
| > | 214 | /* Check we're not hitting max capacity */ | ||
| > | 215 | if (h->primeindex == (prime_table_length - 1)) return 0; | ||
| > | 216 | newsize = primes[++(h->primeindex)]; | ||
| > | 217 | newtable = (whhash_entry **)malloc(sizeof(whhash_entry*) * newsize); | ||
| > | 218 | if (NULL != newtable) | ||
| > | 219 | { | ||
| > | 220 | memset(newtable, 0, newsize * sizeof(whhash_entry *)); | ||
| > | 221 | /* This algorithm is not 'stable'. ie. it reverses the list | ||
| > | 222 | * when it transfers entries between the tables */ | ||
| > | 223 | for (i = 0; i < h->tablelength; i++) { | ||
| > | 224 | while (NULL != (e = h->table[i])) { | ||
| > | 225 | h->table[i] = e->next; | ||
| > | 226 | index = whhash_index(newsize,e->h); | ||
| > | 227 | e->next = newtable[index]; | ||
| > | 228 | newtable[index] = e; | ||
| > | 229 | } | ||
| > | 230 | } | ||
| > | 231 | free(h->table); | ||
| > | 232 | h->table = newtable; | ||
| > | 233 | } | ||
| > | 234 | /* Plan B: realloc instead */ | ||
| > | 235 | else | ||
| > | 236 | { | ||
| > | 237 | newtable = (whhash_entry **) | ||
| > | 238 | realloc(h->table, newsize * sizeof(whhash_entry *)); | ||
| > | 239 | if (NULL == newtable) { (h->primeindex)--; return 0; } | ||
| > | 240 | h->table = newtable; | ||
| > | 241 | memset(newtable[h->tablelength], 0, newsize - h->tablelength); | ||
| > | 242 | for (i = 0; i < h->tablelength; i++) { | ||
| > | 243 | for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE) { | ||
| > | 244 | index = whhash_index(newsize,e->h); | ||
| > | 245 | if (index == i) | ||
| > | 246 | { | ||
| > | 247 | pE = &(e->next); | ||
| > | 248 | } | ||
| > | 249 | else | ||
| > | 250 | { | ||
| > | 251 | *pE = e->next; | ||
| > | 252 | e->next = newtable[index]; | ||
| > | 253 | newtable[index] = e; | ||
| > | 254 | } | ||
| > | 255 | } | ||
| > | 256 | } | ||
| > | 257 | } | ||
| > | 258 | ++h->stats.expansions; | ||
| > | 259 | h->stats.alloced += (sizeof(whhash_entry*) * newsize) | ||
| > | 260 | - (sizeof(whhash_entry*) * h->tablelength); | ||
| > | 261 | h->tablelength = newsize; | ||
| > | 262 | h->loadlimit = whhash_ceil(newsize * max_load_factor); | ||
| > | 263 | return -1; | ||
| > | 264 | } | ||
| > | 265 | |||
| > | 266 | size_t | ||
| > | 267 | whhash_count(whhash_table const * h) | ||
| > | 268 | { | ||
| > | 269 | return h ? h->entrycount : 0; | ||
| > | 270 | } | ||
| > | 271 | |||
| > | 272 | int | ||
| > | 273 | whhash_replace(whhash_table *h, void *k, void *v) | ||
| > | 274 | { | ||
| > | 275 | if( !h || !k | ||
| > | 276 | || !h->tablelength /* avoid a possible /-by-0 in whhash_index()*/ | ||
| > | 277 | ) return 0; | ||
| > | 278 | whhash_entry *e; | ||
| > | 279 | whhash_val_t hashvalue, index; | ||
| > | 280 | hashvalue = whhash_hash(h,k); | ||
| > | 281 | index = whhash_index(h->tablelength,hashvalue); | ||
| > | 282 | e = h->table[index]; | ||
| > | 283 | while (NULL != e) | ||
| > | 284 | { | ||
| > | 285 | /* Check hash value to short circuit heavier comparison */ | ||
| > | 286 | if ( | ||
| > | 287 | (k == e->k) | ||
| > | 288 | || | ||
| > | 289 | ((hashvalue == e->h) && (h->eqfn(k, e->k))) | ||
| > | 290 | ) | ||
| > | 291 | { | ||
| > | 292 | if( v == e->v ) return 1; | ||
| > | 293 | whhash_free_val( h, e->v ); | ||
| > | 294 | e->v = v; | ||
| > | 295 | return -1; | ||
| > | 296 | |||
| > | 297 | } | ||
| > | 298 | e = e->next; | ||
| > | 299 | } | ||
| > | 300 | return 0; | ||
| > | 301 | } | ||
| > | 302 | |||
| > | 303 | int | ||
| > | 304 | whhash_insert(whhash_table *h, void *k, void *v) | ||
| > | 305 | { | ||
| > | 306 | if( ! h || !k | ||
| > | 307 | || !h->tablelength /* avoid a possible /-by-0 in whhash_index()*/ | ||
| > | 308 | ) return 0; | ||
| > | 309 | #if 0 | ||
| > | 310 | /* Stephan Beal, 13 Feb 2008: now simply replaces the value of existing entries. | ||
| > | 311 | |||
| > | 312 | A week or three later: profiling has shown that this nearly doubles the insertion | ||
| > | 313 | time. One app was spending almost 1% of its time in this code. | ||
| > | 314 | */ | ||
| > | 315 | int rc = whhash_replace(h, k, v); | ||
| > | 316 | if( 0 != rc ) return rc; | ||
| > | 317 | #endif | ||
| > | 318 | whhash_val_t index; | ||
| > | 319 | whhash_entry *e; | ||
| > | 320 | if (++(h->entrycount) > h->loadlimit) | ||
| > | 321 | { | ||
| > | 322 | /* Ignore the return value. If expand fails, we should | ||
| > | 323 | * still try cramming just this value into the existing table | ||
| > | 324 | * -- we may not have memory for a larger table, but one more | ||
| > | 325 | * element may be ok. Next time we insert, we'll try expanding again.*/ | ||
| > | 326 | whhash_expand(h); | ||
| > | 327 | } | ||
| > | 328 | e = (whhash_entry *)malloc(sizeof(whhash_entry)); | ||
| > | 329 | if (NULL == e) { --(h->entrycount); return 0; } /*oom*/ | ||
| > | 330 | h->stats.alloced += sizeof(whhash_entry); | ||
| > | 331 | e->h = whhash_hash(h,k); | ||
| > | 332 | index = whhash_index(h->tablelength,e->h); | ||
| > | 333 | e->k = k; | ||
| > | 334 | e->v = v; | ||
| > | 335 | e->next = h->table[index]; | ||
| > | 336 | h->table[index] = e; | ||
| > | 337 | ++h->stats.insertions; | ||
| > | 338 | return 1; | ||
| > | 339 | } | ||
| > | 340 | |||
| > | 341 | static whhash_entry * whhash_search_entry(whhash_table *h, | ||
| > | 342 | void const *k) | ||
| > | 343 | { | ||
| > | 344 | if( !h || !k | ||
| > | 345 | || !h->tablelength /* avoid a possible /-by-0 in whhash_index()*/ | ||
| > | 346 | ) return 0; | ||
| > | 347 | ++h->stats.searches; | ||
| > | 348 | whhash_entry * e = 0; | ||
| > | 349 | whhash_val_t hashvalue, index; | ||
| > | 350 | hashvalue = whhash_hash(h,k); | ||
| > | 351 | index = whhash_index(h->tablelength,hashvalue); | ||
| > | 352 | e = h->table[index]; | ||
| > | 353 | while (NULL != e) | ||
| > | 354 | { | ||
| > | 355 | /* Check hash value to short circuit heavier comparison */ | ||
| > | 356 | if ((k == e->k) || ((hashvalue == e->h) && (h->eqfn(k, e->k)))) | ||
| > | 357 | { | ||
| > | 358 | return e; | ||
| > | 359 | } | ||
| > | 360 | e = e->next; | ||
| > | 361 | ++h->stats.search_collisions; | ||
| > | 362 | } | ||
| > | 363 | return NULL; | ||
| > | 364 | } | ||
| > | 365 | |||
| > | 366 | short whhash_contains(whhash_table *h, void const *k) | ||
| > | 367 | { | ||
| > | 368 | whhash_entry * e = whhash_search_entry(h,k); | ||
| > | 369 | return e ? 1 : 0; | ||
| > | 370 | } | ||
| > | 371 | |||
| > | 372 | void * | ||
| > | 373 | whhash_search(whhash_table *h, void const *k) | ||
| > | 374 | { | ||
| > | 375 | if( !h || !k ) return 0; | ||
| > | 376 | whhash_entry * e = whhash_search_entry(h,k); | ||
| > | 377 | return e ? e->v : 0; | ||
| > | 378 | } | ||
| > | 379 | |||
| > | 380 | void * whhash_take(whhash_table *h, void const *k) | ||
| > | 381 | { | ||
| > | 382 | if( !h || !k | ||
| > | 383 | || !h->tablelength /* avoid a possible /-by-0 in whhash_index()*/ | ||
| > | 384 | ) return 0; | ||
| > | 385 | |||
| > | 386 | /* TODO: consider compacting the table when the load factor drops enough, | ||
| > | 387 | * or provide a 'compact' method. */ | ||
| > | 388 | whhash_entry *e; | ||
| > | 389 | whhash_entry **pE; | ||
| > | 390 | void *v; | ||
| > | 391 | whhash_val_t hashvalue, index; | ||
| > | 392 | // Can we reimplement this using whhash_search_entry()? | ||
| > | 393 | hashvalue = whhash_hash(h,k); | ||
| > | 394 | index = whhash_index(h->tablelength,whhash_hash(h,k)); | ||
| > | 395 | pE = &(h->table[index]); | ||
| > | 396 | e = *pE; | ||
| > | 397 | while (NULL != e) | ||
| > | 398 | { | ||
| > | 399 | /* Check hash value to short circuit heavier comparison */ | ||
| > | 400 | if ( (e->k==k) | ||
| > | 401 | || ((hashvalue == e->h) && (h->eqfn(k, e->k))) | ||
| > | 402 | ) | ||
| > | 403 | { | ||
| > | 404 | *pE = e->next; | ||
| > | 405 | --h->entrycount; | ||
| > | 406 | v = e->v; | ||
| > | 407 | h->stats.alloced -= sizeof(whhash_entry); | ||
| > | 408 | free(e); | ||
| > | 409 | ++h->stats.removals; | ||
| > | 410 | return v; | ||
| > | 411 | } | ||
| > | 412 | pE = &(e->next); | ||
| > | 413 | e = e->next; | ||
| > | 414 | } | ||
| > | 415 | return NULL; | ||
| > | 416 | } | ||
| > | 417 | |||
| > | 418 | short whhash_remove(whhash_table *h, void *k) | ||
| > | 419 | { | ||
| > | 420 | if( !h || !k ) return 0; | ||
| > | 421 | void * v = whhash_take(h,k); | ||
| > | 422 | whhash_free_key(h,k); | ||
| > | 423 | if( v ) | ||
| > | 424 | { | ||
| > | 425 | whhash_free_val(h,v); | ||
| > | 426 | } | ||
| > | 427 | return NULL != v; | ||
| > | 428 | } | ||
| > | 429 | |||
| > | 430 | void whhash_set_val_dtor( whhash_table * h, void (*dtor)( void * ) ) | ||
| > | 431 | { | ||
| > | 432 | if( h ) h->freeVal = dtor; | ||
| > | 433 | } | ||
| > | 434 | void whhash_set_key_dtor( whhash_table * h, void (*dtor)( void * ) ) | ||
| > | 435 | { | ||
| > | 436 | if( h ) h->freeKey = dtor; | ||
| > | 437 | } | ||
| > | 438 | void whhash_set_dtors( whhash_table * h, void (*keyDtor)( void * ), void (*valDtor)( void * ) ) | ||
| > | 439 | { | ||
| > | 440 | whhash_set_key_dtor( h, keyDtor ); | ||
| > | 441 | whhash_set_val_dtor( h, valDtor ); | ||
| > | 442 | } | ||
| > | 443 | |||
| > | 444 | static whhash_entry * whhash_free_entry( whhash_table * h, whhash_entry * e ) | ||
| > | 445 | { | ||
| > | 446 | if( !h || !e ) return 0; | ||
| > | 447 | whhash_entry * next = e->next; | ||
| > | 448 | h->entrycount--; | ||
| > | 449 | whhash_free_key( h, e->k ); | ||
| > | 450 | whhash_free_val( h, e->v ); | ||
| > | 451 | free(e); | ||
| > | 452 | h->stats.alloced -= sizeof(whhash_entry); | ||
| > | 453 | return next; | ||
| > | 454 | } | ||
| > | 455 | |||
| > | 456 | void whhash_clear(whhash_table *h) | ||
| > | 457 | { | ||
| > | 458 | if( ! h || !h->table ) return; | ||
| > | 459 | //printf("whhash_clear(): alloced=%u\n",h->stats.alloced); | ||
| > | 460 | whhash_val_t i; | ||
| > | 461 | whhash_entry *e; | ||
| > | 462 | whhash_entry **table = h->table; | ||
| > | 463 | for (i = 0; i < h->tablelength; i++) | ||
| > | 464 | { | ||
| > | 465 | e = table[i]; | ||
| > | 466 | while (NULL != e) | ||
| > | 467 | { | ||
| > | 468 | e = whhash_free_entry( h, e ); | ||
| > | 469 | } | ||
| > | 470 | } | ||
| > | 471 | //printf("whhash_clear(): alloced=%u\n",h->stats.alloced); | ||
| > | 472 | // Which of these is correct? | ||
| > | 473 | h->stats.alloced -= (sizeof(whhash_entry*) * h->tablelength); | ||
| > | 474 | //printf("tablelen=%u sizeof... = %u\n",h->tablelength,(sizeof(whhash_entry*) * h->tablelength)); | ||
| > | 475 | free(h->table); | ||
| > | 476 | h->table = 0; | ||
| > | 477 | h->entrycount = 0; | ||
| > | 478 | h->tablelength = 0; | ||
| > | 479 | h->loadlimit = 0; | ||
| > | 480 | //printf("whhash_clear(): alloced=%u\n",h->stats.alloced); // why is this 0 instead of sizeof(whhash_table)? | ||
| > | 481 | h->stats.alloced = sizeof(whhash_table); | ||
| > | 482 | //printf("whhash_clear(): alloced=%u\n",h->stats.alloced); | ||
| > | 483 | } | ||
| > | 484 | void | ||
| > | 485 | whhash_destroy(whhash_table *h) | ||
| > | 486 | { | ||
| > | 487 | if( h ) | ||
| > | 488 | { | ||
| > | 489 | whhash_clear(h); | ||
| > | 490 | free(h); | ||
| > | 491 | } | ||
| > | 492 | } | ||
| > | 493 | |||
| > | 494 | whhash_stats whhash_get_stats( whhash_table const * h ) | ||
| > | 495 | { | ||
| > | 496 | return h ? h->stats : whhash_stats_init; | ||
| > | 497 | } | ||
| > | 498 | |||
| > | 499 | int whhash_cmp_cstring( void const * k1, void const * k2 ) | ||
| > | 500 | { | ||
| > | 501 | if( (k1 == k2) ) return 1; | ||
| > | 502 | else if( (!k1 && k2) || (k1 && !k2) ) return 0; /* protect against passing null to strcmp() */ | ||
| > | 503 | return (0 == strcmp( (char const *) k1, (char const *) k2 )); | ||
| > | 504 | } | ||
| > | 505 | |||
| > | 506 | int whhash_cmp_long( void const * k1, void const * k2 ) | ||
| > | 507 | { | ||
| > | 508 | return *((long const*)k1) == *((long const*)k2); | ||
| > | 509 | } | ||
| > | 510 | |||
| > | 511 | int whhash_cmp_void_ptr( void const * k1, void const * k2 ) | ||
| > | 512 | { | ||
| > | 513 | return k1 == k2; | ||
| > | 514 | } | ||
| > | 515 | |||
| > | 516 | whhash_val_t whhash_hash_void_ptr( void const * k ) | ||
| > | 517 | { | ||
| > | 518 | /* This next typedef must *apparently* be the same size as the platform's (void*) */ | ||
| > | 519 | typedef long kludge_t; | ||
| > | 520 | /** | ||
| > | 521 | Originally we used the void* address as a hashvalue, until it | ||
| > | 522 | came to me that those doesn't distribute well within the hash | ||
| > | 523 | range (they tend to clump together). | ||
| > | 524 | |||
| > | 525 | So below we do a variant of the "Modified Bernstein Hash" | ||
| > | 526 | on the bytes of the (void*)'s numeric value. For details: | ||
| > | 527 | |||
| > | 528 | http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx | ||
| > | 529 | |||
| > | 530 | Initial (but minimal) tests show the distribution to be much improved | ||
| > | 531 | over using the (void*) as the hash value. | ||
| > | 532 | */ | ||
| > | 533 | whhash_val_t h = 0; | ||
| > | 534 | kludge_t const x = (kludge_t const) k; | ||
| > | 535 | unsigned char const * c = (unsigned char const *)&x; | ||
| > | 536 | size_t i = 0; | ||
| > | 537 | for( i = 0; i < (sizeof(kludge_t) / sizeof(char)); ++i ) | ||
| > | 538 | { | ||
| > | 539 | h = 33 * h ^ c[i]; | ||
| > | 540 | } | ||
| > | 541 | //MARKER; printf("hash of %p: x=%ld h=%ld, i=%d, sizeof1=%u sizeof2=%u\n",k,x,h,i,sizeof(kludge_t),sizeof(char)); | ||
| > | 542 | return h; | ||
| > | 543 | } | ||
| > | 544 | |||
| > | 545 | |||
| > | 546 | whhash_val_t | ||
| > | 547 | whhash_hash_cstring_djb2( void const * vstr) | ||
| > | 548 | { /* "djb2" algo code taken from: http://www.cse.yorku.ca/~oz/hash.html */ | ||
| > | 549 | if( ! vstr ) return whhash_hash_val_err; | ||
| > | 550 | whhash_val_t hash = 5381; | ||
| > | 551 | int c = 0; | ||
| > | 552 | char const * str = (char const *)vstr; | ||
| > | 553 | if( ! str ) return whhash_hash_val_err; | ||
| > | 554 | while( (c = *str++) ) | ||
| > | 555 | { | ||
| > | 556 | hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ | ||
| > | 557 | } | ||
| > | 558 | return hash; | ||
| > | 559 | } | ||
| > | 560 | |||
| > | 561 | whhash_val_t whhash_hash_cstring_djb2m( void const * vstr ) | ||
| > | 562 | { | ||
| > | 563 | char const * str = (char const *)vstr; | ||
| > | 564 | if( ! str ) return whhash_hash_val_err; | ||
| > | 565 | whhash_val_t h = 0; | ||
| > | 566 | int c = 0; | ||
| > | 567 | while( (c = *(str++)) ) | ||
| > | 568 | { | ||
| > | 569 | h = 33 * h ^ c; | ||
| > | 570 | } | ||
| > | 571 | return h; | ||
| > | 572 | } | ||
| > | 573 | |||
| > | 574 | #if 0 | ||
| > | 575 | /** | ||
| > | 576 | Implements the Fowler/Noll/Vo (FNV) hash, as described at: | ||
| > | 577 | |||
| > | 578 | http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx | ||
| > | 579 | */ | ||
| > | 580 | //whhash_val_t whhash_hash_cstring_fnv( void const * str ); | ||
| > | 581 | whhash_val_t whhash_hash_cstring_fnv( void const * vstr ) | ||
| > | 582 | { | ||
| > | 583 | char const * str = (char const *)vstr; | ||
| > | 584 | if( ! str ) return whhash_hash_val_err; | ||
| > | 585 | whhash_val_t h = (unsigned long long) 2166136261L; | ||
| > | 586 | /** | ||
| > | 587 | ^^^^ | ||
| > | 588 | gcc says: warning: this decimal constant is unsigned only in ISO C90 | ||
| > | 589 | */ | ||
| > | 590 | int c = 0; | ||
| > | 591 | while( (c = *(str++)) ) | ||
| > | 592 | { | ||
| > | 593 | h = (h * 16777619L) ^ c; | ||
| > | 594 | } | ||
| > | 595 | return h; | ||
| > | 596 | } | ||
| > | 597 | #endif | ||
| > | 598 | |||
| > | 599 | whhash_val_t whhash_hash_cstring_oaat( void const * vstr ) | ||
| > | 600 | { | ||
| > | 601 | char const * str = (char const *)vstr; | ||
| > | 602 | if( ! str ) return whhash_hash_val_err; | ||
| > | 603 | whhash_val_t h = 0; | ||
| > | 604 | int c = 0; | ||
| > | 605 | while( (c = *(str++)) ) | ||
| > | 606 | { | ||
| > | 607 | h += c; | ||
| > | 608 | h += (h << 10); | ||
| > | 609 | h ^= (h >> 6); | ||
| > | 610 | } | ||
| > | 611 | h += ( h << 3 ); | ||
| > | 612 | h ^= ( h >> 11 ); | ||
| > | 613 | h += ( h << 15 ); | ||
| > | 614 | return h; | ||
| > | 615 | } | ||
| > | 616 | |||
| > | 617 | |||
| > | 618 | whhash_val_t | ||
| > | 619 | whhash_hash_cstring_sdbm(void const *vstr) | ||
| > | 620 | { /* "sdbm" algo code taken from: http://www.cse.yorku.ca/~oz/hash.html */ | ||
| > | 621 | char const * str = (char const *)vstr; | ||
| > | 622 | if( ! str ) return whhash_hash_val_err; | ||
| > | 623 | whhash_val_t h = 0; | ||
| > | 624 | int c = 0; | ||
| > | 625 | while( str && (c = *str++) ) | ||
| > | 626 | { | ||
| > | 627 | h = c + (h << 6) + (h << 16) - h; | ||
| > | 628 | } | ||
| > | 629 | return h; | ||
| > | 630 | } | ||
| > | 631 | |||
| > | 632 | whhash_val_t | ||
| > | 633 | whhash_hash_cstring_rot( void const * vstr) | ||
| > | 634 | { | ||
| > | 635 | char const * str = (char const *)vstr; | ||
| > | 636 | if( ! str ) return whhash_hash_val_err; | ||
| > | 637 | whhash_val_t h = 0; | ||
| > | 638 | int c = 0; | ||
| > | 639 | while( (c = *(str++)) ) | ||
| > | 640 | { | ||
| > | 641 | h = (h << 4) ^ (h >> 28 ) ^ c; | ||
| > | 642 | } | ||
| > | 643 | return h; | ||
| > | 644 | } | ||
| > | 645 | |||
| > | 646 | whhash_val_t | ||
| > | 647 | whhash_hash_cstring_sax( void const * vstr) | ||
| > | 648 | { | ||
| > | 649 | char const * str = (char const *)vstr; | ||
| > | 650 | if( ! str ) return whhash_hash_val_err; | ||
| > | 651 | whhash_val_t h = 0; | ||
| > | 652 | int c = 0; | ||
| > | 653 | while( (c = *(str++)) ) | ||
| > | 654 | { | ||
| > | 655 | h ^= (h << 5) + (h >> 2) + c; | ||
| > | 656 | } | ||
| > | 657 | return h; | ||
| > | 658 | } | ||
| > | 659 | |||
| > | 660 | |||
| > | 661 | whhash_val_t | ||
| > | 662 | whhash_hash_long( void const * n ) | ||
| > | 663 | { | ||
| > | 664 | return n | ||
| > | 665 | ? *((whhash_val_t const *)n) | ||
| > | 666 | : whhash_hash_val_err; | ||
| > | 667 | } | ||
| > | 668 | |||
| > | 669 | struct whhash_iter | ||
| > | 670 | { | ||
| > | 671 | whhash_table *h; | ||
| > | 672 | whhash_entry *e; | ||
| > | 673 | whhash_entry *parent; | ||
| > | 674 | unsigned int index; | ||
| > | 675 | }; | ||
| > | 676 | static const whhash_iter whhash_itr_init = {0,0,0,0}; | ||
| > | 677 | |||
| > | 678 | whhash_iter * | ||
| > | 679 | whhash_get_iter(whhash_table *h) | ||
| > | 680 | { | ||
| > | 681 | if( !whhash_count(h) ) return 0; | ||
| > | 682 | unsigned int i, tablelength; | ||
| > | 683 | whhash_iter *itr = (whhash_iter *) | ||
| > | 684 | malloc(sizeof(whhash_iter)); | ||
| > | 685 | if (NULL == itr) return NULL; | ||
| > | 686 | h->stats.alloced += sizeof(whhash_iter); | ||
| > | 687 | *itr = whhash_itr_init; | ||
| > | 688 | itr->h = h; | ||
| > | 689 | itr->e = NULL; | ||
| > | 690 | itr->parent = NULL; | ||
| > | 691 | tablelength = h->tablelength; | ||
| > | 692 | itr->index = tablelength; | ||
| > | 693 | //if (0 == h->entrycount) return itr; | ||
| > | 694 | |||
| > | 695 | for (i = 0; i < tablelength; i++) | ||
| > | 696 | { | ||
| > | 697 | if (NULL != h->table[i]) | ||
| > | 698 | { | ||
| > | 699 | itr->e = h->table[i]; | ||
| > | 700 | itr->index = i; | ||
| > | 701 | break; | ||
| > | 702 | } | ||
| > | 703 | } | ||
| > | 704 | return itr; | ||
| > | 705 | } | ||
| > | 706 | |||
| > | 707 | void * | ||
| > | 708 | whhash_iter_key(whhash_iter *i) | ||
| > | 709 | { return i ? i->e->k : 0; } | ||
| > | 710 | |||
| > | 711 | void * | ||
| > | 712 | whhash_iter_value(whhash_iter *i) | ||
| > | 713 | { return i ? i->e->v : 0; } | ||
| > | 714 | |||
| > | 715 | int | ||
| > | 716 | whhash_iter_advance(whhash_iter *itr) | ||
| > | 717 | { | ||
| > | 718 | unsigned int j,tablelength; | ||
| > | 719 | whhash_entry **table; | ||
| > | 720 | whhash_entry *next; | ||
| > | 721 | if (!itr || (!itr->e)) return 0; | ||
| > | 722 | |||
| > | 723 | next = itr->e->next; | ||
| > | 724 | if (NULL != next) | ||
| > | 725 | { | ||
| > | 726 | itr->parent = itr->e; | ||
| > | 727 | itr->e = next; | ||
| > | 728 | return -1; | ||
| > | 729 | } | ||
| > | 730 | tablelength = itr->h->tablelength; | ||
| > | 731 | itr->parent = NULL; | ||
| > | 732 | if (tablelength <= (j = ++(itr->index))) | ||
| > | 733 | { | ||
| > | 734 | itr->e = NULL; | ||
| > | 735 | return 0; | ||
| > | 736 | } | ||
| > | 737 | table = itr->h->table; | ||
| > | 738 | while (NULL == (next = table[j])) | ||
| > | 739 | { | ||
| > | 740 | if (++j >= tablelength) | ||
| > | 741 | { | ||
| > | 742 | itr->index = tablelength; | ||
| > | 743 | itr->e = NULL; | ||
| > | 744 | return 0; | ||
| > | 745 | } | ||
| > | 746 | } | ||
| > | 747 | itr->index = j; | ||
| > | 748 | itr->e = next; | ||
| > | 749 | return -1; | ||
| > | 750 | } | ||
| > | 751 | |||
| > | 752 | /*****************************************************************************/ | ||
| > | 753 | /* remove - remove the entry at the current iterator position | ||
| > | 754 | * and advance the iterator, if there is a successive | ||
| > | 755 | * element. | ||
| > | 756 | * The key and value of the element are freed using | ||
| > | 757 | * whhash_free_key/val(), which may or may not actually | ||
| > | 758 | * free them. | ||
| > | 759 | * Returns zero if end of iteration. | ||
| > | 760 | */ | ||
| > | 761 | |||
| > | 762 | int | ||
| > | 763 | whhash_iter_remove(whhash_iter *itr) | ||
| > | 764 | { | ||
| > | 765 | whhash_entry *remember_e, *remember_parent; | ||
| > | 766 | int ret = 0; | ||
| > | 767 | |||
| > | 768 | /* Do the removal */ | ||
| > | 769 | if (NULL == (itr->parent)) | ||
| > | 770 | { | ||
| > | 771 | /* element is head of a chain */ | ||
| > | 772 | itr->h->table[itr->index] = itr->e->next; | ||
| > | 773 | } else { | ||
| > | 774 | /* element is mid-chain */ | ||
| > | 775 | itr->parent->next = itr->e->next; | ||
| > | 776 | } | ||
| > | 777 | /* itr->e is now outside the whhash_table */ | ||
| > | 778 | remember_e = itr->e; | ||
| > | 779 | itr->h->entrycount--; | ||
| > | 780 | whhash_free_key( itr->h, remember_e->k ); | ||
| > | 781 | whhash_free_val( itr->h, remember_e->v ); | ||
| > | 782 | |||
| > | 783 | /* Advance the iterator, correcting the parent */ | ||
| > | 784 | remember_parent = itr->parent; | ||
| > | 785 | ret = whhash_iter_advance(itr); | ||
| > | 786 | if (itr->parent == remember_e) { itr->parent = remember_parent; } | ||
| > | 787 | free(remember_e); | ||
| > | 788 | itr->h->stats.alloced -= sizeof(whhash_entry); | ||
| > | 789 | return ret; | ||
| > | 790 | } | ||
| > | 791 | |||
| > | 792 | int | ||
| > | 793 | whhash_iter_search(whhash_iter *itr, | ||
| > | 794 | void *k) | ||
| > | 795 | { | ||
| > | 796 | if( ! itr || !itr->h || !k | ||
| > | 797 | || !itr->h->tablelength /* avoid a possible /-by-0 in whhash_index()*/ | ||
| > | 798 | ) return 0; | ||
| > | 799 | whhash_entry *e, *parent; | ||
| > | 800 | unsigned int hashvalue, index; | ||
| > | 801 | whhash_table *h = itr->h; | ||
| > | 802 | hashvalue = whhash_hash(h,k); | ||
| > | 803 | index = whhash_index(h->tablelength,hashvalue); | ||
| > | 804 | e = h->table[index]; | ||
| > | 805 | parent = NULL; | ||
| > | 806 | while (NULL != e) | ||
| > | 807 | { | ||
| > | 808 | /* Check hash value to short circuit heavier comparison */ | ||
| > | 809 | if ((e->k == k) | ||
| > | 810 | || | ||
| > | 811 | ((hashvalue == e->h) && (h->eqfn(k, e->k))) | ||
| > | 812 | ) | ||
| > | 813 | { | ||
| > | 814 | itr->index = index; | ||
| > | 815 | itr->e = e; | ||
| > | 816 | itr->parent = parent; | ||
| > | 817 | itr->h = h; | ||
| > | 818 | return -1; | ||
| > | 819 | } | ||
| > | 820 | parent = e; | ||
| > | 821 | e = e->next; | ||
| > | 822 | } | ||
| > | 823 | return 0; | ||
| > | 824 | } | ||
| > | 825 | |||
| > | 826 | #ifdef __cplusplus | ||
| > | 827 | } /* extern "C" */ | ||
| > | 828 | #endif | ||
| > | 829 | |||
| > | 830 | /* | ||
| > | 831 | Copyright (c) 2002, Christopher Clark | ||
| > | 832 | Copyright (C) 2008 Stephan Beal (http://wanderinghorse.net/home/stephan/) | ||
| > | 833 | All rights reserved. | ||
| > | 834 | |||
| > | 835 | Redistribution and use in source and binary forms, with or without | ||
| > | 836 | modification, are permitted provided that the following conditions | ||
| > | 837 | are met: | ||
| > | 838 | |||
| > | 839 | * Redistributions of source code must retain the above copyright | ||
| > | 840 | notice, this list of conditions and the following disclaimer. | ||
| > | 841 | |||
| > | 842 | * Redistributions in binary form must reproduce the above copyright | ||
| > | 843 | notice, this list of conditions and the following disclaimer in the | ||
| > | 844 | documentation and/or other materials provided with the distribution. | ||
| > | 845 | |||
| > | 846 | * Neither the name of the original author; nor the names of any contributors | ||
| > | 847 | may be used to endorse or promote products derived from this software | ||
| > | 848 | without specific prior written permission. | ||
| > | 849 | |||
| > | 850 | |||
| > | 851 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| > | 852 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| > | 853 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| > | 854 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER | ||
| > | 855 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | ||
| > | 856 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | ||
| > | 857 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||
| > | 858 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||
| > | 859 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||
| > | 860 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
| > | 861 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| > | 862 | */ | ||
Added whhash.h
| Old () | New (b966a1a75384615d) | |||
|---|---|---|---|---|
| > | 1 | /* Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */ | ||
| > | 2 | /* Copyright (C) 2008 Stephan Beal (http://wanderinghorse.net/home/stephan/) */ | ||
| > | 3 | /* Code originally taken from: http://www.cl.cam.ac.uk/~cwc22/hashtable/ */ | ||
| > | 4 | #ifndef WANDERINGHORSE_NET_WHHASH_H_INCLUDED | ||
| > | 5 | #define WANDERINGHORSE_NET_WHHASH_H_INCLUDED | ||
| > | 6 | #include <stddef.h> /* size_t */ | ||
| > | 7 | #ifdef __cplusplus | ||
| > | 8 | extern "C" { | ||
| > | 9 | #endif | ||
| > | 10 | /** | ||
| > | 11 | @page whhash_page_main whhash: hashtable API | ||
| > | 12 | |||
| > | 13 | The functions and types named whhash* are part of the | ||
| > | 14 | WanderingHorse.net hashtable library. It is a hashtable | ||
| > | 15 | implementation based on code by Christopher Clark, | ||
| > | 16 | adopted, extended, and changed somewhat by yours truly. | ||
| > | 17 | |||
| > | 18 | License: New BSD License | ||
| > | 19 | |||
| > | 20 | Maintainer: Stephan Beal (http://wanderinghorse.net/home/stephan) | ||
| > | 21 | |||
| > | 22 | The hashtables described here map (void*) to (void*) by using a | ||
| > | 23 | client-supplied hash algorithm on the key pointers. The hashtable | ||
| > | 24 | can optionally take over ownership of its keys or values, via | ||
| > | 25 | whhash_set_key_dtor() and whhash_set_key_dtor(). The ownership | ||
| > | 26 | management option makes this type useful as a simple garbage | ||
| > | 27 | collector. | ||
| > | 28 | |||
| > | 29 | @section whhash_sec_example Example | ||
| > | 30 | |||
| > | 31 | @code | ||
| > | 32 | whhash_table *h; | ||
| > | 33 | struct some_key *k; | ||
| > | 34 | struct some_value *v; | ||
| > | 35 | |||
| > | 36 | // Assume the following functions which can hash and compare the | ||
| > | 37 | // some_key and some_val structs: | ||
| > | 38 | static whhash_val_t hash_some_key( void *k ); | ||
| > | 39 | static int cmp_key_val ( void *key1, void *key2 ); | ||
| > | 40 | |||
| > | 41 | h = whhash_create(16, hash_some_key, cmp_key_val); | ||
| > | 42 | k = (struct some_key *) malloc(sizeof(struct some_key)); | ||
| > | 43 | v = (struct some_value *) malloc(sizeof(struct some_value)); | ||
| > | 44 | |||
| > | 45 | ...initialise k and v to suitable values... | ||
| > | 46 | |||
| > | 47 | if (! whhash_insert(h,k,v) ) | ||
| > | 48 | { | ||
| > | 49 | ...error... probably OOM or one of the args was null...; | ||
| > | 50 | } | ||
| > | 51 | |||
| > | 52 | if (NULL == (found = whhash_search(h,k) )) | ||
| > | 53 | { printf("not found!"); } | ||
| > | 54 | if (NULL == (found = whhash_take(h,k) )) | ||
| > | 55 | { printf("Not found\n"); } | ||
| > | 56 | |||
| > | 57 | whhash_destroy( h ); | ||
| > | 58 | @endcode | ||
| > | 59 | |||
| > | 60 | @section whhash_sec_ownership Memory ownership | ||
| > | 61 | |||
| > | 62 | By default a hashtable does no memory management of its keys or | ||
| > | 63 | values. However, whhash_set_key_dtor() and whhash_set_val_dtor() | ||
| > | 64 | can be used to set a cleanup function. If a cleanup function is | ||
| > | 65 | set, it is called (and passed the appropriate key or value) when an | ||
| > | 66 | item is removed from the hashtable (e.g. when the hashtable is | ||
| > | 67 | destroyed). It is possible to transfer ownership back to the caller | ||
| > | 68 | by using whhash_take(). | ||
| > | 69 | |||
| > | 70 | Example: | ||
| > | 71 | |||
| > | 72 | @code | ||
| > | 73 | whhash_table *h; | ||
| > | 74 | h = whhash_create(16, whhash_hash_cstring_djb2, whhash_cmp_cstring ); | ||
| > | 75 | whhash_set_dtors(h, free, free); | ||
| > | 76 | char * str = 0; | ||
| > | 77 | char * key = 0; | ||
| > | 78 | int i = 0; | ||
| > | 79 | for( int i = 0; i < 10; ++i ) | ||
| > | 80 | { | ||
| > | 81 | // Assume mnprintf() is a printf-like func which allocates new strings: | ||
| > | 82 | key = mnprintf("key_%d",i); | ||
| > | 83 | str = mnprintf("...%d...",i); | ||
| > | 84 | whhash_insert(h, key, str ); // transfers ownership of key/str to h | ||
| > | 85 | } | ||
| > | 86 | whhash_destroy( h ); // Calls free() on each inserted copy of key and str. | ||
| > | 87 | @endcode | ||
| > | 88 | |||
| > | 89 | |||
| > | 90 | @section whhash_sec_macros Macros | ||
| > | 91 | |||
| > | 92 | Macros may be used to define type-safe(r) whhash_table access functions, with | ||
| > | 93 | methods specialized to take known key and value types as parameters. | ||
| > | 94 | |||
| > | 95 | Example: | ||
| > | 96 | |||
| > | 97 | Insert this at the start of your file: | ||
| > | 98 | |||
| > | 99 | @code | ||
| > | 100 | DEFINE_WHHASH_INSERT(insert_some, struct some_key, struct some_value); | ||
| > | 101 | DEFINE_WHHASH_SEARCH(search_some, struct some_key, struct some_value); | ||
| > | 102 | DEFINE_WHHASH_TAKE(take_some, struct some_key); | ||
| > | 103 | DEFINE_WHHASH_REMOVE(remove_some, struct some_key); | ||
| > | 104 | @endcode | ||
| > | 105 | |||
| > | 106 | This defines the functions 'insert_some', 'search_some', | ||
| > | 107 | 'take_some', and 'remove_some'. These operate just like | ||
| > | 108 | whhash_insert() etc., with the same parameters, but their function | ||
| > | 109 | signatures have 'struct some_key *' rather than 'void *', and | ||
| > | 110 | hence can generate compile time errors if your program is | ||
| > | 111 | supplying incorrect data as a key (and similarly for value). | ||
| > | 112 | |||
| > | 113 | Note that the hash and key equality functions passed to | ||
| > | 114 | whhash_create still take 'void *' parameters instead of 'some key | ||
| > | 115 | *'. This shouldn't be a difficult issue as they're only defined | ||
| > | 116 | and passed once, and the other functions will ensure that only | ||
| > | 117 | valid keys are supplied to them. | ||
| > | 118 | |||
| > | 119 | The cost for this checking is increased code size and runtime | ||
| > | 120 | overhead - if performance is important, it may be worth switching | ||
| > | 121 | back to the unsafe methods once your program has been debugged | ||
| > | 122 | with the safe methods. This just requires switching to some | ||
| > | 123 | simple alternative defines - eg: | ||
| > | 124 | |||
| > | 125 | @code | ||
| > | 126 | #define insert_some whhash_insert | ||
| > | 127 | @endcode | ||
| > | 128 | |||
| > | 129 | @section whhash_sec_threadsafety Thread safety | ||
| > | 130 | |||
| > | 131 | By default it is never legal to use the same hashtable from | ||
| > | 132 | multiple threads at the same time. That said, the client may use | ||
| > | 133 | their own locking to serialize access. All API functions which | ||
| > | 134 | take a (whhash_table const *) argument require only a read lock, | ||
| > | 135 | whereas those taking a (whhash_table*) argument require a | ||
| > | 136 | exclusive (read/write) access. whhash_create() is reentrant and | ||
| > | 137 | does not need to be locked. | ||
| > | 138 | |||
| > | 139 | Special consideration is also needed considering locking of stored | ||
| > | 140 | keys/values. If a referenced item is used by multiple threads, | ||
| > | 141 | this code has no way of knowing about it. When in doubt, don't | ||
| > | 142 | store items which are shared across threads in a hashtable unless | ||
| > | 143 | you know that lifetime and ownership issues can be mitigated. If, | ||
| > | 144 | e.g., a hashtable does not own its entries, one needs to make sure | ||
| > | 145 | that if the entry is deleted from somewhere else, that it's | ||
| > | 146 | removed from the hashtable (or ensure that that entry cannot be | ||
| > | 147 | references again, otherwise you'll get a dangling pointer back). | ||
| > | 148 | |||
| > | 149 | |||
| > | 150 | @section whhash_sec_links Other resources | ||
| > | 151 | |||
| > | 152 | Some other resources: | ||
| > | 153 | |||
| > | 154 | - The original implementation off of which whhash is based can be found | ||
| > | 155 | at: http://www.cl.cam.ac.uk/~cwc22/hashtable/ | ||
| > | 156 | |||
| > | 157 | - A good intro to hashtables: | ||
| > | 158 | http://en.wikipedia.org/wiki/Hash_table | ||
| > | 159 | |||
| > | 160 | - Brief analysis and implementations of of several well-known hash routines: | ||
| > | 161 | http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx | ||
| > | 162 | */ | ||
| > | 163 | |||
| > | 164 | /** @typedef whhash_val_t | ||
| > | 165 | |||
| > | 166 | The hash key value type by the whhash API. | ||
| > | 167 | */ | ||
| > | 168 | typedef unsigned long whhash_val_t; | ||
| > | 169 | |||
| > | 170 | /** | ||
| > | 171 | @var const whhash_val_t whhash_hash_val_err | ||
| > | 172 | |||
| > | 173 | whhash_hash_val_err is ((whhash_val_t)-1). It is used to report | ||
| > | 174 | hash value errors. Hash routines which want to report | ||
| > | 175 | an error may do so by returning this value. | ||
| > | 176 | */ | ||
| > | 177 | extern const whhash_val_t whhash_hash_val_err; | ||
| > | 178 | /** @struct whhash_table | ||
| > | 179 | |||
| > | 180 | whhash_table is an opaque handle to hashtable data. They are | ||
| > | 181 | created using whhash_create() and destroyed using | ||
| > | 182 | whhash_destroy(). | ||
| > | 183 | */ | ||
| > | 184 | struct whhash_table; | ||
| > | 185 | typedef struct whhash_table whhash_table; | ||
| > | 186 | |||
| > | 187 | |||
| > | 188 | /** | ||
| > | 189 | whhash_create() allocates a new hashtable with the given minimum | ||
| > | 190 | starting size (the actual size may, and probably will, be | ||
| > | 191 | larger). The given hash function and comparison function are used | ||
| > | 192 | for all hashing and comparisons, respectively. | ||
| > | 193 | |||
| > | 194 | The comparison function must return 0 if no match is made, else | ||
| > | 195 | non-zero. The hash function must create a hash value for the given | ||
| > | 196 | object, the more randomly distributed the better. | ||
| > | 197 | |||
| > | 198 | minsize specifies the minimum initial size of the hashtable. It may | ||
| > | 199 | (and probably will) allocate more than this, however. | ||
| > | 200 | */ | ||
| > | 201 | whhash_table * whhash_create(whhash_val_t minsize, | ||
| > | 202 | whhash_val_t (*hashfunction) (void const *), | ||
| > | 203 | int (*key_eq_fn) (void const *,void const *)); | ||
| > | 204 | |||
| > | 205 | |||
| > | 206 | /** | ||
| > | 207 | Sets the destructor function for h's keys, which are cleaned up | ||
| > | 208 | when items are removed or the whhash_table is destroyed. By default | ||
| > | 209 | a whhash_table owns its keys and will call free() to release | ||
| > | 210 | them. If you use keys from managed memory and don't want them | ||
| > | 211 | destroyed by the whhash_table, simply pass 0 as the dtor argument. | ||
| > | 212 | */ | ||
| > | 213 | void whhash_set_key_dtor( whhash_table * h, void (*dtor)( void * ) ); | ||
| > | 214 | |||
| > | 215 | /** | ||
| > | 216 | This is similar to whhash_set_key_dtor(), but applies to | ||
| > | 217 | whhash_table values instead of keys. By default a whhash_table does | ||
| > | 218 | not own its values and will not delete them under any | ||
| > | 219 | circumstances. To make the whhash_table take ownership, simply pass | ||
| > | 220 | 'free' (or suitable function for your type, e.g. many structs may | ||
| > | 221 | need a custom destructor) to this function. | ||
| > | 222 | */ | ||
| > | 223 | void whhash_set_val_dtor( whhash_table * h, void (*dtor)( void * ) ); | ||
| > | 224 | |||
| > | 225 | /** | ||
| > | 226 | Equivalent to whhash_set_key_dtor(h,keyDtor) and | ||
| > | 227 | whhash_set_val_dtor(h,valDtor). | ||
| > | 228 | */ | ||
| > | 229 | void whhash_set_dtors( whhash_table * h, void (*keyDtor)( void * ), void (*valDtor)( void * ) ); | ||
| > | 230 | |||
| > | 231 | |||
| > | 232 | /** | ||
| > | 233 | whhash_insert() adds new entries to a hashtable. | ||
| > | 234 | |||
| > | 235 | Neither of the h or k parameters may be 0. A value of 0 is okay but | ||
| > | 236 | in practice it means a client cannot differentiate between "not | ||
| > | 237 | found" and "found value of 0". The objects pointed to by the | ||
| > | 238 | parameters must outlive this hashtable (or be managed/owned by it by | ||
| > | 239 | assigning destructor functions, e.g. via whhash_set_dtors()). | ||
| > | 240 | |||
| > | 241 | When a key is re-inserted (already mapped to something) then results | ||
| > | 242 | are undefined. When in doubt you should use whhash_take() to remove | ||
| > | 243 | the entry before re-adding it (but then freeing the old value becomes | ||
| > | 244 | your responsibility). (Historical note: some versions of this code | ||
| > | 245 | automatically replaced existing items, but profiling shows it to | ||
| > | 246 | nearly double the insertion costs so this feature was removed.) | ||
| > | 247 | |||
| > | 248 | This function will cause the table to expand if the insertion would | ||
| > | 249 | take the ratio of entries to table size over the maximum load factor. | ||
| > | 250 | |||
| > | 251 | Key/value ownership: By default a hashtable does not own its keys nor | ||
| > | 252 | its values. You may set the ownership policy by using | ||
| > | 253 | whhash_set_key_dtor() and whhash_set_val_dtor(). The destructors are | ||
| > | 254 | called whenever items are removed from the whhash_table or the | ||
| > | 255 | whhash_table is destroyed. | ||
| > | 256 | |||
| > | 257 | The key object must not change in a way which affects its hash value | ||
| > | 258 | after it is inserted. To do so invokes undefined behaviour. | ||
| > | 259 | |||
| > | 260 | That the key is not a const parameter is unfortunate, but it's the | ||
| > | 261 | only way we can properly apply ownership management without violating | ||
| > | 262 | constness. The original intention of this type was a hash which owned its | ||
| > | 263 | keys, which is why the parameter has historically not been const. Current | ||
| > | 264 | usage of this class ranges from general purpose lookup (without memory | ||
| > | 265 | management) to garbage collector, and some of those uses cannot be achieved | ||
| > | 266 | with a const key. | ||
| > | 267 | */ | ||
| > | 268 | int | ||
| > | 269 | whhash_insert(whhash_table *h, void *k, void *v); | ||
| > | 270 | |||
| > | 271 | /** | ||
| > | 272 | whhash_replace() changes the value associated with a key, where there | ||
| > | 273 | already exists a value bound to the key in the whhash_table. If (v == | ||
| > | 274 | existingValue) then this routine has no side effects, otherwise this | ||
| > | 275 | function calls whhash_free_val() for any existing value tied to | ||
| > | 276 | k, so it may (or may not) deallocate an object. | ||
| > | 277 | |||
| > | 278 | Source by Holger Schemel. Modified by Stephan Beal to use | ||
| > | 279 | whhash_free_value(). | ||
| > | 280 | |||
| > | 281 | Returns 0 if no match is found, -1 if a match is made and replaced, | ||
| > | 282 | and 1 if (v == existingValue). | ||
| > | 283 | |||
| > | 284 | Neither the h nor k parameters may be 0. | ||
| > | 285 | */ | ||
| > | 286 | int | ||
| > | 287 | whhash_replace(whhash_table *h, void *k, void *v); | ||
| > | 288 | |||
| > | 289 | #define DEFINE_WHHASH_INSERT(fnname, keytype, valuetype) \ | ||
| > | 290 | int fnname (whhash_table *h, keytype *k, valuetype *v) \ | ||
| > | 291 | { \ | ||
| > | 292 | return whhash_insert(h,k,v); \ | ||
| > | 293 | } | ||
| > | 294 | |||
| > | 295 | /** | ||
| > | 296 | whhash_search() searches for the given key and returns the | ||
| > | 297 | associated value (if found) or 0 (if not found). Ownership of the | ||
| > | 298 | returned value is unchanged. | ||
| > | 299 | */ | ||
| > | 300 | void * | ||
| > | 301 | whhash_search(whhash_table *h, void const * k); | ||
| > | 302 | |||
| > | 303 | /** | ||
| > | 304 | Works like whhash_search() but can differentiate between a found | ||
| > | 305 | value of 0 and no-such-element. | ||
| > | 306 | |||
| > | 307 | Returns 0 for if wh does not contain the given key and non-zero for | ||
| > | 308 | if wh does contain the key. | ||
| > | 309 | |||
| > | 310 | Maintainer's note: the wh parameter should be const, but its not | ||
| > | 311 | for internal reasons. | ||
| > | 312 | */ | ||
| > | 313 | short whhash_contains(whhash_table *wh, void const * k); | ||
| > | 314 | |||
| > | 315 | |||
| > | 316 | #define DEFINE_WHHASH_SEARCH(fnname, keytype, valuetype) \ | ||
| > | 317 | valuetype * fnname (whhash_table *h, keytype const *k) \ | ||
| > | 318 | { \ | ||
| > | 319 | return (valuetype *) (whhash_search(h,k)); \ | ||
| > | 320 | } | ||
| > | 321 | |||
| > | 322 | /** | ||
| > | 323 | whhash_take() removes the given key from the whhash_table and | ||
| > | 324 | returns the value to the caller. The key/value destructors (if any) | ||
| > | 325 | set via whhash_set_key_dtor() and whhash_set_val_dtor() WILL | ||
| > | 326 | NOT be called! If you want to remove an item and call the dtors for | ||
| > | 327 | its key and value, use whhash_remove(). | ||
| > | 328 | |||
| > | 329 | If (!h), (!k), or no match is found, then 0 is returned and ownership | ||
| > | 330 | of k is not changed. If a match is found which is 0, 0 is | ||
| > | 331 | still returned but ownership of k is returned to the caller. There | ||
| > | 332 | is unfortunately no way for a caller to differentiate between these | ||
| > | 333 | two cases. | ||
| > | 334 | |||
| > | 335 | */ | ||
| > | 336 | void * whhash_take(whhash_table *h, void const *k); | ||
| > | 337 | |||
| > | 338 | /** | ||
| > | 339 | Works like whhash_take(h,k), but also calls the dtors registered | ||
| > | 340 | for the key and value, which may (or may not) deallocate the | ||
| > | 341 | objects. Since the destructor may (or may not) destroy the key, k | ||
| > | 342 | may not (or may) be valid after this function returns. | ||
| > | 343 | |||
| > | 344 | Returns 0 if it finds no entry to remove, else non-zero. | ||
| > | 345 | */ | ||
| > | 346 | short whhash_remove(whhash_table *h, void *k); | ||
| > | 347 | |||
| > | 348 | #define DEFINE_WHHASH_TAKE(fnname, keytype, valuetype) \ | ||
| > | 349 | valuetype * fnname (whhash_table *h, keytype const *k) \ | ||
| > | 350 | { \ | ||
| > | 351 | return (valuetype *) (whhash_take(h,k)); \ | ||
| > | 352 | } | ||
| > | 353 | |||
| > | 354 | #define DEFINE_WHHASH_REMOVE(fnname, keytype) \ | ||
| > | 355 | short fnname (whhash_table *h, keytype const *k) { return whhash_remove(h,k);} | ||
| > | 356 | |||
| > | 357 | |||
| > | 358 | /*! | ||
| > | 359 | whhash_count() returns the number of items in the hashtable. | ||
| > | 360 | This is a constant-time operation. | ||
| > | 361 | */ | ||
| > | 362 | size_t | ||
| > | 363 | whhash_count(whhash_table const * h); | ||
| > | 364 | |||
| > | 365 | |||
| > | 366 | /** | ||
| > | 367 | whhash_destroy() cleans up resources allocated by a whhash_table and calls | ||
| > | 368 | the configured destructors for each key and value. After | ||
| > | 369 | this call, h is invalid. | ||
| > | 370 | */ | ||
| > | 371 | void whhash_destroy(whhash_table *h); | ||
| > | 372 | |||
| > | 373 | /** | ||
| > | 374 | Similar to whhash_destroy(), but only cleans up the internal | ||
| > | 375 | contents, and does not actualy delete the h pointer. Thus h | ||
| > | 376 | can be used for further inserts. The next insert will force | ||
| > | 377 | the hashtable to re-allocate internal storage. This routine | ||
| > | 378 | is the only way to get a whhash_table to reduce its size. | ||
| > | 379 | |||
| > | 380 | This routine will force any registered dtors to be called on | ||
| > | 381 | each key/value in the hashtable. | ||
| > | 382 | */ | ||
| > | 383 | void whhash_clear(whhash_table *h); | ||
| > | 384 | |||
| > | 385 | /** | ||
| > | 386 | A comparison function for use with whhash_create(). If (k1==k2) it | ||
| > | 387 | returns non-0 (true), otherwise it performs strcmp(k1,k2) and | ||
| > | 388 | returns true if they match. | ||
| > | 389 | |||
| > | 390 | This function accepts null pointers. Two null pointers will compare | ||
| > | 391 | equal, otherwise a single null pointer in the pair will evaluate | ||
| > | 392 | to false. | ||
| > | 393 | */ | ||
| > | 394 | int whhash_cmp_cstring( void const * k1, void const * k2 ); | ||
| > | 395 | |||
| > | 396 | |||
| > | 397 | /** | ||
| > | 398 | A comparison function for use with whhash_create(). | ||
| > | 399 | It essentially performs (*((long*)k1) == (*((long*)k2))). | ||
| > | 400 | |||
| > | 401 | Trying to compare numbers of different-sized types (e.g. long and | ||
| > | 402 | short) won't work (well, probably won't). Results are undefined. | ||
| > | 403 | */ | ||
| > | 404 | int whhash_cmp_long( void const * k1, void const * k2 ); | ||
| > | 405 | |||
| > | 406 | /** | ||
| > | 407 | An int/long hashing function for use with whhash_create(). It | ||
| > | 408 | requires that n point to a long integer, and it simply returns the | ||
| > | 409 | value of n, or whhash_hash_val_err on error (n is NULL). | ||
| > | 410 | */ | ||
| > | 411 | whhash_val_t whhash_hash_long( void const * n ); | ||
| > | 412 | |||
| > | 413 | /** | ||
| > | 414 | This is a hash routine for generic void pointers. To avoid clustering | ||
| > | 415 | of hashvalues, it performs the following: | ||
| > | 416 | |||
| > | 417 | - Casts k to its numeric value (as a long, or some other platform-specific | ||
| > | 418 | numeric type). | ||
| > | 419 | - Performs a Berstein Hash on the first (sizeof(long)/sizeof(char)) bytes | ||
| > | 420 | of that numeric value, treating each byte as a separate input character. | ||
| > | 421 | |||
| > | 422 | That greatly improves the distribution range of the hash values over using | ||
| > | 423 | the address of k as the hash value. | ||
| > | 424 | */ | ||
| > | 425 | whhash_val_t whhash_hash_void_ptr( void const * k ); | ||
| > | 426 | |||
| > | 427 | /** | ||
| > | 428 | A hashtable comparison function which simply returns (k1 == k2). | ||
| > | 429 | */ | ||
| > | 430 | int whhash_cmp_void_ptr( void const * k1, void const * k2 ); | ||
| > | 431 | |||
| > | 432 | /** | ||
| > | 433 | A C-string hashing function for use with whhash_create(). Uses the | ||
| > | 434 | so-called "djb2" algorithm. Returns whhash_hash_val_err if (!str). | ||
| > | 435 | |||
| > | 436 | For notes on the hash algorithm see: | ||
| > | 437 | |||
| > | 438 | http://www.cse.yorku.ca/~oz/hash.html | ||
| > | 439 | |||
| > | 440 | */ | ||
| > | 441 | whhash_val_t whhash_hash_cstring_djb2( void const * str ); | ||
| > | 442 | |||
| > | 443 | /** | ||
| > | 444 | Implements the "Modified Bernstein Hash", as described at: | ||
| > | 445 | |||
| > | 446 | http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx | ||
| > | 447 | |||
| > | 448 | */ | ||
| > | 449 | whhash_val_t whhash_hash_cstring_djb2m( void const * str ); | ||
| > | 450 | |||
| > | 451 | |||
| > | 452 | /** | ||
| > | 453 | Implements the "Shift-Add-XOR" (SAX) hash, as described at: | ||
| > | 454 | |||
| > | 455 | http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx | ||
| > | 456 | */ | ||
| > | 457 | whhash_val_t whhash_hash_cstring_djb2m( void const * str ); | ||
| > | 458 | |||
| > | 459 | |||
| > | 460 | /** | ||
| > | 461 | Implements the One-at-a-time hash, as described at: | ||
| > | 462 | |||
| > | 463 | http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx | ||
| > | 464 | */ | ||
| > | 465 | whhash_val_t whhash_hash_cstring_oaat( void const * str ); | ||
| > | 466 | |||
| > | 467 | /** | ||
| > | 468 | The "rotating hash", as described at: | ||
| > | 469 | |||
| > | 470 | http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx | ||
| > | 471 | |||
| > | 472 | To quote that page: | ||
| > | 473 | |||
| > | 474 | "Much of the time, the rotating hash is sufficient, and can be | ||
| > | 475 | considered the minimal acceptable algorithm." | ||
| > | 476 | */ | ||
| > | 477 | whhash_val_t whhash_hash_cstring_rot( void const * str ); | ||
| > | 478 | |||
| > | 479 | /** | ||
| > | 480 | A C-string hashing function for use with whhash_create(). Uses the | ||
| > | 481 | so-called "sdbm" algorithm. Returns whhash_hash_val_err if (!str). | ||
| > | 482 | |||
| > | 483 | For notes on the hash algorithm see: | ||
| > | 484 | |||
| > | 485 | http://www.cse.yorku.ca/~oz/hash.html | ||
| > | 486 | */ | ||
| > | 487 | whhash_val_t whhash_hash_cstring_sdbm( void const * str ); | ||
| > | 488 | |||
| > | 489 | |||
| > | 490 | /*! @struct whhash_iter | ||
| > | 491 | |||
| > | 492 | Opaque handle for whhash_table interators. | ||
| > | 493 | */ | ||
| > | 494 | struct whhash_iter; | ||
| > | 495 | typedef struct whhash_iter whhash_iter; | ||
| > | 496 | |||
| > | 497 | |||
| > | 498 | /** | ||
| > | 499 | whhash_get_iter() creates a new iterator for the given hashtable | ||
| > | 500 | and returns it. The caller must call free() on the object when he | ||
| > | 501 | is done with it. If (!whhash_count(h)) then this function returns | ||
| > | 502 | 0. | ||
| > | 503 | */ | ||
| > | 504 | whhash_iter * whhash_get_iter(whhash_table *h); | ||
| > | 505 | |||
| > | 506 | /** | ||
| > | 507 | Returns the key of the (key,value) pair at the current position, | ||
| > | 508 | or 0 if !i. | ||
| > | 509 | */ | ||
| > | 510 | void * whhash_iter_key(whhash_iter *i); | ||
| > | 511 | |||
| > | 512 | /** | ||
| > | 513 | Returns the value of the (key,value) pair at the current position, | ||
| > | 514 | or 0 if !i. | ||
| > | 515 | */ | ||
| > | 516 | void * whhash_iter_value(whhash_iter *i); | ||
| > | 517 | |||
| > | 518 | /** | ||
| > | 519 | Advance the iterator to the next element. Returns zero if advanced to | ||
| > | 520 | end of table or if (!itr). | ||
| > | 521 | */ | ||
| > | 522 | int whhash_iter_advance(whhash_iter *itr); | ||
| > | 523 | |||
| > | 524 | /** | ||
| > | 525 | Removes current element and advance the iterator to the | ||
| > | 526 | next element the associated destructors to free (or not) the | ||
| > | 527 | pointed-to key and value, so this call may (or may not) deallocate | ||
| > | 528 | those objects. | ||
| > | 529 | */ | ||
| > | 530 | int whhash_iter_remove(whhash_iter *itr); | ||
| > | 531 | |||
| > | 532 | /** | ||
| > | 533 | Searches for the given key in itr's associated hashtable. | ||
| > | 534 | If found, itr is modified to point to the found item and | ||
| > | 535 | a true value is returned. If no item is found or if (!itr||!k) | ||
| > | 536 | then false (0) is returned. | ||
| > | 537 | */ | ||
| > | 538 | int whhash_iter_search(whhash_iter *itr, void *k); | ||
| > | 539 | |||
| > | 540 | #define DEFINE_WHHASH_ITERATOR_SEARCH(fnname, keytype) \ | ||
| > | 541 | int fnname (whhash_iter *i, keytype *k) \ | ||
| > | 542 | { \ | ||
| > | 543 | return (whhash_iter_search(i,k)); \ | ||
| > | 544 | } | ||
| > | 545 | |||
| > | 546 | /** | ||
| > | 547 | The whhash_stats struct is used for telemetry collection for | ||
| > | 548 | whhash_table objects. These objects should be created by calling | ||
| > | 549 | whhash_get_stats(). | ||
| > | 550 | */ | ||
| > | 551 | struct whhash_stats | ||
| > | 552 | { | ||
| > | 553 | /** | ||
| > | 554 | Number of entries in the context. | ||
| > | 555 | */ | ||
| > | 556 | size_t entries; | ||
| > | 557 | |||
| > | 558 | /** | ||
| > | 559 | Number of registrations made in the context. | ||
| > | 560 | */ | ||
| > | 561 | size_t insertions; | ||
| > | 562 | |||
| > | 563 | /** | ||
| > | 564 | Number of whhash_take() calls made in the context. | ||
| > | 565 | */ | ||
| > | 566 | size_t removals; | ||
| > | 567 | |||
| > | 568 | /** | ||
| > | 569 | The number of searches made on the hashtable. | ||
| > | 570 | */ | ||
| > | 571 | size_t searches; | ||
| > | 572 | |||
| > | 573 | /** | ||
| > | 574 | A rough *approximatation* of amount of memory allocated for | ||
| > | 575 | whgc-specific internal structures used by the context, | ||
| > | 576 | including the size of the underlying hashtable(s). A | ||
| > | 577 | context has no way of knowing how much memory is used by | ||
| > | 578 | registered items. | ||
| > | 579 | |||
| > | 580 | Don't take this number too seriously. i try to keep the | ||
| > | 581 | telemetry up to date for allocs, but keeping track of | ||
| > | 582 | deallocs is more difficult due to timing and scope issues. | ||
| > | 583 | */ | ||
| > | 584 | size_t alloced; | ||
| > | 585 | /** | ||
| > | 586 | The number of times the size of the hashtable has been | ||
| > | 587 | increased beyond the initial value. | ||
| > | 588 | */ | ||
| > | 589 | size_t expansions; | ||
| > | 590 | |||
| > | 591 | /** | ||
| > | 592 | This is incremented each time a search operation fails | ||
| > | 593 | to find the proper entry on the first try. If this | ||
| > | 594 | number is large, the hash function may need to be | ||
| > | 595 | changed. | ||
| > | 596 | */ | ||
| > | 597 | size_t search_collisions; | ||
| > | 598 | }; | ||
| > | 599 | typedef struct whhash_stats whhash_stats; | ||
| > | 600 | |||
| > | 601 | /** | ||
| > | 602 | Returns the current statistics for h, or an object | ||
| > | 603 | with all 0 values if (!h). | ||
| > | 604 | */ | ||
| > | 605 | whhash_stats whhash_get_stats( whhash_table const * h ); | ||
| > | 606 | |||
| > | 607 | #ifdef __cplusplus | ||
| > | 608 | } /* extern "C" */ | ||
| > | 609 | #endif | ||
| > | 610 | #endif /* WANDERINGHORSE_NET_WHHASH_H_INCLUDED */ | ||
| > | 611 | |||
| > | 612 | /* | ||
| > | 613 | Copyright (c) 2002, Christopher Clark | ||
| > | 614 | Copyright (C) 2008 Stephan Beal (http://wanderinghorse.net/home/stephan/) | ||
| > | 615 | All rights reserved. | ||
| > | 616 | |||
| > | 617 | Redistribution and use in source and binary forms, with or without | ||
| > | 618 | modification, are permitted provided that the following conditions | ||
| > | 619 | are met: | ||
| > | 620 | |||
| > | 621 | * Redistributions of source code must retain the above copyright | ||
| > | 622 | notice, this list of conditions and the following disclaimer. | ||
| > | 623 | |||
| > | 624 | * Redistributions in binary form must reproduce the above copyright | ||
| > | 625 | notice, this list of conditions and the following disclaimer in the | ||
| > | 626 | documentation and/or other materials provided with the distribution. | ||
| > | 627 | |||
| > | 628 | * Neither the name of the original author; nor the names of any contributors | ||
| > | 629 | may be used to endorse or promote products derived from this software | ||
| > | 630 | without specific prior written permission. | ||
| > | 631 | |||
| > | 632 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| > | 633 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| > | 634 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| > | 635 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER | ||
| > | 636 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | ||
| > | 637 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | ||
| > | 638 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||
| > | 639 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||
| > | 640 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||
| > | 641 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
| > | 642 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| > | 643 | */ | ||
Added whrc.c
| Old () | New (2e21d9d2ad366f20) | |||
|---|---|---|---|---|
| > | 1 | #include <stdlib.h> /* malloc/free */ | ||
| > | 2 | #include <assert.h> | ||
| > | 3 | |||
| > | 4 | #include "whrc.h" | ||
| > | 5 | #include "whhash.h" | ||
| > | 6 | #ifdef __cplusplus | ||
| > | 7 | extern "C" { | ||
| > | 8 | #endif | ||
| > | 9 | |||
| > | 10 | #if 1 | ||
| > | 11 | #include <stdio.h> // only for debuggering | ||
| > | 12 | #define MARKER if(1) printf("MARKER: %s:%d:%s():\n",__FILE__,__LINE__,__func__); if(1) printf | ||
| > | 13 | #else | ||
| > | 14 | static void bogo_printf(char const * fmt, ...) {} | ||
| > | 15 | #define MARKER if(0) bogo_printf | ||
| > | 16 | #endif | ||
| > | 17 | |||
| > | 18 | const size_t whrc_ref_err_val = ((size_t)-1); | ||
| > | 19 | struct whrc_context | ||
| > | 20 | { | ||
| > | 21 | whhash_table * ht; | ||
| > | 22 | }; | ||
| > | 23 | |||
| > | 24 | static const whrc_context whrc_context_init = | ||
| > | 25 | { | ||
| > | 26 | 0 /* ht */ | ||
| > | 27 | }; | ||
| > | 28 | |||
| > | 29 | struct whrc_entry | ||
| > | 30 | { | ||
| > | 31 | whrc_dtor_f dtor; | ||
| > | 32 | size_t refcount; | ||
| > | 33 | }; | ||
| > | 34 | typedef struct whrc_entry whrc_entry; | ||
| > | 35 | static const whrc_entry whrc_entry_init = {0,0}; | ||
| > | 36 | |||
| > | 37 | whrc_context * whrc_create_context() | ||
| > | 38 | { | ||
| > | 39 | whrc_context * c = (whrc_context *)malloc(sizeof(whrc_context)); | ||
| > | 40 | if( ! c ) return 0; | ||
| > | 41 | *c = whrc_context_init; | ||
| > | 42 | if( (c->ht = whhash_create( 10, whhash_hash_void_ptr, whhash_cmp_void_ptr ) ) ) | ||
| > | 43 | { | ||
| > | 44 | whhash_set_dtors( c->ht, 0, free ); | ||
| > | 45 | } | ||
| > | 46 | if( ! c->ht ) | ||
| > | 47 | { | ||
| > | 48 | free(c); | ||
| > | 49 | return 0; | ||
| > | 50 | } | ||
| > | 51 | return c; | ||
| > | 52 | } | ||
| > | 53 | |||
| > | 54 | void whrc_clear_context( whrc_context * cx ) | ||
| > | 55 | { | ||
| > | 56 | whhash_iter * it = cx ? whhash_get_iter(cx->ht) : 0; | ||
| > | 57 | //MARKER; | ||
| > | 58 | if( ! it ) return; | ||
| > | 59 | //MARKER; | ||
| > | 60 | do | ||
| > | 61 | { | ||
| > | 62 | //MARKER; | ||
| > | 63 | whrc_entry * e = (whrc_entry *)whhash_iter_value(it); | ||
| > | 64 | if( ! e ) continue; /* shouldn't happen! */ | ||
| > | 65 | void * item = whhash_iter_key(it); | ||
| > | 66 | if( item && e->dtor ) | ||
| > | 67 | { | ||
| > | 68 | //MARKER; | ||
| > | 69 | e->dtor( item ); | ||
| > | 70 | } | ||
| > | 71 | } | ||
| > | 72 | while( whhash_iter_advance(it) ); | ||
| > | 73 | free(it); | ||
| > | 74 | whhash_clear(cx->ht); | ||
| > | 75 | } | ||
| > | 76 | |||
| > | 77 | void whrc_destroy_context( whrc_context * cx, bool freeItems ) | ||
| > | 78 | { | ||
| > | 79 | if( ! cx ) return; | ||
| > | 80 | if( freeItems ) whrc_clear_context(cx); | ||
| > | 81 | whhash_destroy( cx->ht ); | ||
| > | 82 | free(cx); | ||
| > | 83 | } | ||
| > | 84 | |||
| > | 85 | bool whrc_register( whrc_context * cx, void * item, whrc_dtor_f dtor ) | ||
| > | 86 | { | ||
| > | 87 | if( ! cx || !item ) return false; | ||
| > | 88 | if( whrc_is_registered(cx,item) ) return false; | ||
| > | 89 | whrc_entry * e = (whrc_entry*)malloc(sizeof(whrc_entry)); | ||
| > | 90 | if( ! e ) return false; | ||
| > | 91 | *e = whrc_entry_init; | ||
| > | 92 | e->refcount = 1; | ||
| > | 93 | e->dtor = dtor; | ||
| > | 94 | if( ! whhash_insert( cx->ht, item, e ) ) | ||
| > | 95 | { | ||
| > | 96 | free(e); | ||
| > | 97 | e = 0; | ||
| > | 98 | } | ||
| > | 99 | return 0 != e; | ||
| > | 100 | } | ||
| > | 101 | |||
| > | 102 | static whrc_entry * whrc_search_entry( whrc_context * cx, void const * item ) | ||
| > | 103 | { | ||
| > | 104 | return (cx && item) | ||
| > | 105 | ? (whrc_entry *) whhash_search(cx->ht, item) | ||
| > | 106 | : 0; | ||
| > | 107 | } | ||
| > | 108 | |||
| > | 109 | #if 0 | ||
| > | 110 | /** | ||
| > | 111 | This searches the context for the given item and returns it (or 0 if | ||
| > | 112 | no match is found (i.e. the item is not registered)). | ||
| > | 113 | |||
| > | 114 | Why use an item to look up itself? The first reason is, this routine | ||
| > | 115 | can be used in place to whrc_is_registered() (though that one is | ||
| > | 116 | slightly more efficient). | ||
| > | 117 | |||
| > | 118 | Secondly it can sometimes be used to legally get a non-const pointer | ||
| > | 119 | to an object which is otherwise const (that is, without casting away | ||
| > | 120 | the constness). For example, one routine may register the item, then | ||
| > | 121 | downstream the item is passed as a const pointer to another | ||
| > | 122 | routine. That routine can (assuming it has the whrc_context handle) | ||
| > | 123 | then use whrc_search() to get a non-const pointer to the item. | ||
| > | 124 | Admittedly only rarely useful, but this approach has come in handy a | ||
| > | 125 | time or two. | ||
| > | 126 | */ | ||
| > | 127 | //void * whrc_search( whrc_context * cx, void const * item ); | ||
| > | 128 | static void * whrc_search( whrc_context * cx, void const * item ) | ||
| > | 129 | { | ||
| > | 130 | whrc_entry * e = whrc_search_entry(cx,item); | ||
| > | 131 | return e | ||
| > | 132 | ? e->item | ||
| > | 133 | : 0; | ||
| > | 134 | } | ||
| > | 135 | #endif | ||
| > | 136 | |||
| > | 137 | |||
| > | 138 | size_t whrc_refcount( whrc_context * cx, void const * item ) | ||
| > | 139 | { | ||
| > | 140 | whrc_entry const * e = whrc_search_entry(cx, item); | ||
| > | 141 | if( ! e ) return whrc_ref_err_val; | ||
| > | 142 | return e->refcount; | ||
| > | 143 | } | ||
| > | 144 | |||
| > | 145 | |||
| > | 146 | bool whrc_is_registered( whrc_context * cx, void const * item ) | ||
| > | 147 | { | ||
| > | 148 | return 0 != whrc_search_entry(cx,item); | ||
| > | 149 | } | ||
| > | 150 | |||
| > | 151 | static whrc_entry * whrc_take_entry( whrc_context * cx, void * item ) | ||
| > | 152 | { | ||
| > | 153 | return cx | ||
| > | 154 | ? (whrc_entry *) whhash_take(cx->ht, item) | ||
| > | 155 | : 0; | ||
| > | 156 | } | ||
| > | 157 | |||
| > | 158 | size_t whrc_addref( whrc_context * cx, void * item ) | ||
| > | 159 | { | ||
| > | 160 | whrc_entry * e = whrc_search_entry(cx,item); | ||
| > | 161 | if( ! e ) return whrc_ref_err_val; | ||
| > | 162 | return ++e->refcount; | ||
| > | 163 | } | ||
| > | 164 | |||
| > | 165 | size_t whrc_rmref( whrc_context * cx, void * item ) | ||
| > | 166 | { | ||
| > | 167 | whrc_entry * e = whrc_search_entry(cx,item); | ||
| > | 168 | //MARKER("e=@%p, rc=%u\n",e,e?e->refcount:whrc_ref_err_val); | ||
| > | 169 | if( ! e ) return whrc_ref_err_val; | ||
| > | 170 | if( 0 == --e->refcount ) | ||
| > | 171 | { | ||
| > | 172 | whrc_entry * check = whrc_take_entry(cx, item); | ||
| > | 173 | assert( (check == e) && "Internal state error - it seems another thread has trashed our hashtable!"); | ||
| > | 174 | /** | ||
| > | 175 | We do this copy/free thing for the case that the dtor | ||
| > | 176 | throws a C++ exception or otherwise doesn't return (e.g. it | ||
| > | 177 | jumps). i know it's not likely, but it's easy enough to | ||
| > | 178 | avoid the leak here, so i do... | ||
| > | 179 | */ | ||
| > | 180 | whrc_entry E = *check; | ||
| > | 181 | free(check); | ||
| > | 182 | if( E.dtor ) | ||
| > | 183 | { | ||
| > | 184 | E.dtor( item ); | ||
| > | 185 | } | ||
| > | 186 | return 0; | ||
| > | 187 | } | ||
| > | 188 | return e->refcount; | ||
| > | 189 | } | ||
| > | 190 | |||
| > | 191 | #ifdef __cplusplus | ||
| > | 192 | } /* extern "C" */ | ||
| > | 193 | #endif | ||
Added whrc.h
| Old () | New (a16dc9ab135125fb) | |||
|---|---|---|---|---|
| > | 1 | #ifndef WANDERINGHORSE_NET_WHRC_H_INCLUDED | ||
| > | 2 | #define WANDERINGHORSE_NET_WHRC_H_INCLUDED 1 | ||
| > | 3 | /** @page whrc_page_main whrc: reference counting library for C | ||
| > | 4 | |||
| > | 5 | whrc (the WanderingHorse.net Reference Counter) is a C library for | ||
| > | 6 | adding reference counts to arbitrary void pointers. It allows one to | ||
| > | 7 | register a destructor function which will be called when the reference | ||
| > | 8 | count goes to 0. It is similar to the whgc garbage collector | ||
| > | 9 | (see: @ref whgc_page_main) | ||
| > | 10 | but is lighter-weight and does not manage key/value | ||
| > | 11 | pairs as that library does. | ||
| > | 12 | |||
| > | 13 | It internally uses a hashtable which uses a hash code based on the | ||
| > | 14 | (void*) address of an item. In theory the hashtable can average 0(1) | ||
| > | 15 | speeds, so there is very little performance hit. | ||
| > | 16 | |||
| > | 17 | Example: | ||
| > | 18 | @code | ||
| > | 19 | whrc_context * cx = whrc_create_context(); | ||
| > | 20 | char * str = (char *)malloc(...); | ||
| > | 21 | whrc_register( cx, str, free ); | ||
| > | 22 | size_t rc = whrc_refcount(cx,str); // rc == 1 | ||
| > | 23 | rc = whrc_addref(cx,str); //rc == 2 | ||
| > | 24 | rc = whrc_rmref(cx,str); // rc == 1 | ||
| > | 25 | rc = whrc_rmref(cx,str); // rc == 0, free(str) is called | ||
| > | 26 | whrc_destroy_context(cx,true); | ||
| > | 27 | @endcode | ||
| > | 28 | |||
| > | 29 | (The bool argument to whrc_destroy_context() says whether or not to | ||
| > | 30 | free up any "dangling" items (those with references) in the context.) | ||
| > | 31 | |||
| > | 32 | That demonstrates the majority of the API. There are only a handful of | ||
| > | 33 | functions to learn. | ||
| > | 34 | |||
| > | 35 | @section whrc_sec_threadsafety Thread safety | ||
| > | 36 | |||
| > | 37 | By default it is never legal to use the same @ref whrc_context from | ||
| > | 38 | multiple threads at the same time. That said, the client may use | ||
| > | 39 | their own locking to serialize access. All API functions which | ||
| > | 40 | take a (@ref whrc_context const *) argument require only a read lock, | ||
| > | 41 | whereas those taking a (@ref whrc_context *) argument require a | ||
| > | 42 | exclusive (read/write) access. whrc_create_context() is reentrant | ||
| > | 43 | and does not need to be locked. | ||
| > | 44 | |||
| > | 45 | Special consideration is also needed considering locking of stored | ||
| > | 46 | items. If a referenced item is used by multiple threads, this code | ||
| > | 47 | has no way of knowing about it. When in doubt, don't store items | ||
| > | 48 | which are shared across threads unless you know that lifetime and | ||
| > | 49 | ownership issues can be mitigated. | ||
| > | 50 | */ | ||
| > | 51 | |||
| > | 52 | #include <stddef.h> /* size_t */ | ||
| > | 53 | |||
| > | 54 | #ifdef __cplusplus | ||
| > | 55 | extern "C" { | ||
| > | 56 | #endif | ||
| > | 57 | |||
| > | 58 | #ifndef __cplusplus | ||
| > | 59 | # ifndef WHGC_HAVE_STDBOOL | ||
| > | 60 | # define WHGC_HAVE_STDBOOL 0 | ||
| > | 61 | # endif | ||
| > | 62 | # if defined(WHRC_HAVE_STDBOOL) && !(WHRC_HAVE_STDBOOL) | ||
| > | 63 | # if !defined(bool) | ||
| > | 64 | # define bool char | ||
| > | 65 | # endif | ||
| > | 66 | # if !defined(true) | ||
| > | 67 | # define true 1 | ||
| > | 68 | # endif | ||
| > | 69 | # if !defined(false) | ||
| > | 70 | # define false 0 | ||
| > | 71 | # endif | ||
| > | 72 | # else /* aha! stdbool.h! C99. */ | ||
| > | 73 | # include <stdbool.h> | ||
| > | 74 | # endif /* WHRC_HAVE_STDBOOL */ | ||
| > | 75 | #endif /* __cplusplus */ | ||
| > | 76 | |||
| > | 77 | /** @typedef void (*whrc_dtor_f)(void*) | ||
| > | 78 | |||
| > | 79 | A destructor function semantically compatible with free(). | ||
| > | 80 | */ | ||
| > | 81 | typedef void (*whrc_dtor_f)(void*); | ||
| > | 82 | |||
| > | 83 | /** | ||
| > | 84 | Defined to ((size_t)-1), and returned from some functions | ||
| > | 85 | which need to report an error. | ||
| > | 86 | */ | ||
| > | 87 | extern const size_t whrc_ref_err_val; | ||
| > | 88 | |||
| > | 89 | /**@typedef struct whrc_context whrc_context | ||
| > | 90 | |||
| > | 91 | Opaque handle type for reference count operations. | ||
| > | 92 | */ | ||
| > | 93 | struct whrc_context; | ||
| > | 94 | typedef struct whrc_context whrc_context; | ||
| > | 95 | |||
| > | 96 | /** | ||
| > | 97 | Creates a new whrc_context object and transfers ownership to the | ||
| > | 98 | caller. The caller must free it with whrc_destroy_context(). | ||
| > | 99 | */ | ||
| > | 100 | whrc_context * whrc_create_context(); | ||
| > | 101 | |||
| > | 102 | /** | ||
| > | 103 | Clears all items in a context, unconditionally passing them to | ||
| > | 104 | their registered destructors. | ||
| > | 105 | */ | ||
| > | 106 | void whrc_clear_context( whrc_context * ); | ||
| > | 107 | |||
| > | 108 | /** | ||
| > | 109 | Frees all resources allocated by the context. If freeItems is true | ||
| > | 110 | then whrc_clear_context() is called, otherwise any remaining items | ||
| > | 111 | in the cache are left untouched (and will leak unless ownership of | ||
| > | 112 | the items has been explicitly transfered). | ||
| > | 113 | */ | ||
| > | 114 | void whrc_destroy_context( whrc_context * cx, bool freeItems ); | ||
| > | 115 | |||
| > | 116 | /** | ||
| > | 117 | Registers item with cx and sets its reference count to 1. | ||
| > | 118 | It is not legal to register the same item more than once. | ||
| > | 119 | |||
| > | 120 | Returns true if the item is now registered (and therefore | ||
| > | 121 | owned by this object) or false if !cx, !item, or if an entry | ||
| > | 122 | is already found for the given key. | ||
| > | 123 | |||
| > | 124 | Note that it is legal to set a 0 dtor, in which case this | ||
| > | 125 | object does not own the item but will still maintain a reference | ||
| > | 126 | count. In any case, item must outlive the context or be destroyed | ||
| > | 127 | by the context. | ||
| > | 128 | */ | ||
| > | 129 | bool whrc_register( whrc_context * cx, void * item, whrc_dtor_f ); | ||
| > | 130 | |||
| > | 131 | /** | ||
| > | 132 | Returns true only if item is registered in the given context. | ||
| > | 133 | */ | ||
| > | 134 | bool whrc_is_registered( whrc_context * cx, void const * item ); | ||
| > | 135 | |||
| > | 136 | |||
| > | 137 | /** | ||
| > | 138 | Adds one to the reference count of item and returns the new | ||
| > | 139 | reference count, or whrc_ref_err_val if (!cx), (!item), or | ||
| > | 140 | if no entry is found. | ||
| > | 141 | */ | ||
| > | 142 | size_t whrc_addref( whrc_context * cx, void * item ); | ||
| > | 143 | |||
| > | 144 | /** | ||
| > | 145 | Subtracts one to the reference count of item and returns the new | ||
| > | 146 | reference count, or whrc_ref_err_val if (!cx) or (!item), or | ||
| > | 147 | if no entry is found. If 0 is returned then the dtor registered | ||
| > | 148 | via whrc_register() is called and passed item. | ||
| > | 149 | */ | ||
| > | 150 | size_t whrc_rmref( whrc_context * cx, void * item ); | ||
| > | 151 | |||
| > | 152 | /** | ||
| > | 153 | Returns the current refcount for the given item, or whrc_ref_err_val | ||
| > | 154 | if no such item is found or if cx or item are 0. | ||
| > | 155 | */ | ||
| > | 156 | size_t whrc_refcount( whrc_context * cx, void const * item ); | ||
| > | 157 | |||
| > | 158 | |||
| > | 159 | #ifdef __cplusplus | ||
| > | 160 | } /* extern "C" */ | ||
| > | 161 | #endif | ||
| > | 162 | |||
| > | 163 | |||
| > | 164 | #endif /* WANDERINGHORSE_NET_WHRC_H_INCLUDED */ | ||