乔卡
一个D语言的nogc实用库.它设计针对C风格的管理内存,并作为D标准库的轻量补充.
cpp
//数组,打印和串插值.
import joka;
void main() {
auto numbers = List!int(4, 6, 8);
scope (exit) numbers.free();
foreach (i, number; numbers) {
println(i"[$(i)]: $(number)");
}
}
为什么是乔卡
1,极简主义:避免太多抽象
2,模块化:最小跨模块依赖
3,速度:极快的编译时间
4,BetterC:-betterC -i完全兼容
5,wasm:与Wasmtime和Emscripten配合良好
性能基准测试
以下是在运行Ubuntu,16GB内存的Ryzen32200G(配备16GB内存)上,Jokas``动态数组与其他流行库添加并移除50,000,000整数时的对比.略了.
下面还有一些使用类似工作负载的高级跨语言结果.这些不是直接的基准,仅用来参考:
cpp
Testing: ./app_d
real 0.09
user 0.02
sys 0.06
Testing: ./app_rs
real 0.09
user 0.03
sys 0.06
Testing: ./app_zig
real 0.09
user 0.03
sys 0.06
Testing: ./app_odin
real 0.14
user 0.05
sys 0.08
快速入门
这里展示了如何使用DUB安装Joka.创建一个新目录,在以下命令中运行:
cpp
dub init -n
dub add joka
就这些.复制粘贴其中一例,确保一切正常.
文献信息
先从示例目录开始,快速了解一下.还可用DDOX来创建包含以下内容的概览:
cpp
git clone --depth=1 https://github.com/Kapendev/joka joka_package
cd joka_package
dub run -b ddox
模块
1,joka.configs:配置助手
2,joka.game:游戏开发助手
3,joka.io:输入和输出过程
4,joka.math:数学
5,joka.memory:内存工具和容器
6,joka.microui:带Joka更改的RxisUI库
7,joka.ranges:区间工具
8,joka.stdc:C``标准库函数
9,joka.types:常见类型定义和美标串
10,joka.ui:用户接口库
11,joka.warena:wasm圆形
12,joka.wasip1:wasm系统接口预览1
13,joka.wasm4:WASM-4奇幻控制台接口
14,joka.wit:Wasm``接口类型助手
版本
1,JokaCustomMemory:允许声明自定义``分配函数.
2,JokaGcMemory:类似JokaCustomMemory,但默认为使用D垃集器.
3,JokaSmallFootprint:在Joka中为部分静态缓冲使用更少的内存.
4,JokaTypesStubs:移除types.d模块中的string.h依赖,改用内部块.在使用wasm时非常有用.
5,JokaMemoryStubs:移除memory.d模块中的stdlib.h依赖,改用内部块.在使用wasm时非常有用.
6,JokaMathStubs:移除math.d模块中的math.h依赖,改用内部块.在使用wasm时非常有用.
7,JokaMallocOnly:禁止用像jokaMake*此类单一分配函数.
8,WASM4:类似WASI,但针对WASM-4奇幻主机.
类型
这并不是个详尽的列表.所有可用类型见源.
基础
1,IndexedValue:索引+值
2,KeyValuePair:键+值
3,GenIndex:生成索引
4,ForeignSlice:非D切片
5,BitSet:静态位集
6,Fault:错误码
7,也许Maybe:可选值+错误码
8,选项Option:可选值(一般偏好"可能")
9,结果Result:成功或错误值(一般更喜欢"也许")
10,联Union:标记联
11,PathSepStyle:路径分隔符
12,StrPair:串对
数组
1,列表List:动态数组
2,BufferList:固定缓冲数组
3,FixedList:固定大小数组
4,SparseList:稀疏数组
5,GenList:生成数组
6,位表BitList:动态位数组
7,栅Grid:二维数组
分配器
1,圆形:固定线性分配器
2,GrowingArena:增长线性分配器
3,内存环境MemoryContext:配置器API
数学
1,Vec2:二维向量
2,Vec3:三维向量
3,Vec4:四维向量
4,RGBA:RGBA``颜色
5,矩形Rect:二维矩形
6,圆Circ:二维圆
7,行Line:二维行
8,圆整Rounding:圆整函数.
9,缓解Easing:缓解功能.
10,TweenMode:补间循环行为
11,Tween:1D补间.
12,Tween2:2D补间
13,平滑切换SmoothToggle:平滑转换态
区间
1,数值区间NumericRange:数字序列
2,ArrayRange:数组视图
3,枚举区间EnumeratedRange:索引序列
4,映射区间MapRange:映射区间
5,FilterRange:过滤区间
6,TakeRange:取区间
7,链区间ChainRange:链区间
8,StrideRange:步区间
9,累加区间AccumulateRange:累加区间
10,ChunksRange:Chunks区间
11,SplitterRange:分割区间
12,ArgTokenRange:命令行参数
别名
cpp
Sz: size_t
Pd: ptrdiff_t
Str: char[]
IStr: const(char)[]
Strz: char*
IStrz: const(char)*
游戏
1,翻转Flip:翻转方向
2,磁帖Tile:游戏磁帖
3,TileMap:游戏地图
4,SpriteAnimation:动画数据
5,精灵Sprite:动画精灵
6,GTimer:常见计时器
7,故事Story:对话系统
8,方框Box:小矩形
9,BoxWorld:Arcady``物理引擎
wasm
Joka通过-betterC -i标志支持wasm,并与Emscripten和Wasmtime整合.也可通过允许"块"在其他环境中使用一些Joka模块.
下面是一个WASI的你好示例:
cpp
import joka.wasip1;
import joka.types;
extern(C)
void _start() {
auto value = 40 + 29;
auto text = "Value is: {}\n".fmt(value).toCiovec();
fdWrite(stdout, &text, 1, null);
}
编译并运行``Wasmtime:
cpp
ldc2 -betterC -i --mtriple=wasm32 --checkaction=halt app.d
wasmtime app.wasm
此外,可用wasm32-wasi目标来默认启用某些版本,如默认JokaMemoryStubs,并取得WASI特定的实现:
cpp
import joka.io;
extern(C)
void _start() {
println("Value is: ", 40 + 29);
}
ldc2 -betterC -i --mtriple=wasm32-wasi app.d
wasmtime app.wasm
脚本文件夹中还提供了基础的WASM-4模板.开始时,在你的项目目录中复制该目录的内容.WASM-4项应遵守以下结构:
cpp
my_wasm4_project/
├── app.d
├── joka/
└── run
内存跟踪
Joka包含一个轻量内存跟踪系统,可检测调试构建中的泄漏或无效释放.默认,助手函数``memoryTrackingInfo``输出如下略.
上述泄漏摘要可过滤,只显示包含过滤串的路径的泄漏.如,memoryTrackingInfo("app.d")只显示路径中带"app.d"的泄漏.可用ignoreLeak忽略具体分配,比如这样:
cpp
// struct Game { int hp; int mp; }
// Game* game;
game = jokaMake!Game().ignoreLeak();
也可分组分配,这样更好理解在ScopedAllocationGroup中每个分配的用法,如下:
cpp
//也可用`"bestartAllocationGroup"`和`"endAllocationGroup"``函数实现`.
with (ScopedAllocationGroup("World")) {
allocateMonsters();
allocateActors();
with (ScopedAllocationGroup("Contents")) {
allocateItems();
allocateEvents();
}
}
allocateText();
//不属于任何团体.
可用静态(isTrackingMemory)内存检测跟踪是否激活,如果激活了,可用_memoryTrackingState检查当前跟踪状态.
_memoryTrackingState是线本的,因此每线程都有自己的独立跟踪状态.
独立的内存.d
完全可不完全依赖Joka,直接使用``分配内存模块.为此,复制memory.d及types.d到一个项目中.
独立数学.d
也可不完全依赖Joka,直接用数学模块.复制math.d和types.d到项目里.
常见问题解答
Joka有分配器API吗
是的.看看memory.d里的MemoryContext.Joka``默认设计成类C标准库,但许多数据结构确实接受可选的分配器.
乔卡有像Jai那样的全局环境吗
是,而且它故意给它取了一个难听的名字(__memoryContext),目的是让人们不敢使用.原因在于,全局环境往往使得低层API脆弱.在乔卡中,鼓励仅在特例下使用.
相比Jai,Jokas版本只是管理内存.以下是一些相关信息:
cpp
struct MemoryContext {
void* allocatorState;
AllocatorReallocFunc reallocFunc;
void* malloc(Sz alignment, Sz size, IStr file, Sz line);
void* realloc(Sz alignment, void* oldPtr, Sz oldSize, Sz newSize, IStr file, Sz line);
void free(Sz alignment, void* oldPtr, Sz oldSize, IStr file, Sz line);
}
alias AllocatorReallocFunc = void* function(void* allocatorState, Sz alignment, void* oldPtr, Sz oldSize, Sz newSize, IStr file, Sz line);
struct ScopedMemoryContext {
MemoryContext _previousMemoryContext;
this(MemoryContext newContext);
this(ref Arena arena);
this(ref GrowingArena arena);
}
ScopedMemoryContext ScopedDefaultMemoryContext();
void jokaRestoreDefaultAllocatorSetup(ref MemoryContext context);
void jokaRestoreNullAllocatorSetup(ref MemoryContext context);
void jokaEnsureCapture(ref MemoryContext capture);
MemoryContext __memoryContext;
有些类型,比如列表,会跟踪他们使用的分配器.拥有分配器的成员一般叫抓成员.建议在使用抓前先调用jokaEnsureCapture.
也可用jokaSystem*函数忽略环境.如,GrowingArena类型使用了jokaSystemMalloc和jokaSystemFree.
拦截第三方代码
该系统的一个理由是可"拦截第三方代码".别.环境系统是一个需要考虑的全局变量.它类似带笔的颜色的PICO-8的API,但用来管理内存和区间神奇.
为什么有些函数不用@nogc
因为D垃集器可用JokaGcMemory版本来分配内存.@nogc属性只是给编译器一个提示,告诉它检查,调用函数是否也带该提示.它有帮助,但对写无GC代码来说不必.
如,考虑该函数:
cpp
char[] temporaryString() {
static char[64][32] buffers = void;
static currentBuffer = 0;
currentBuffer = (currentBuffer + 1) % buffers.length;
return buffers[currentBuffer][];
}
该函数使用静态缓冲在运行时创建临时串.它从不用GC分配,因此在实际操作中是nogc函数,但不是@nogc函数.
如果你试从@nogc``调用函数它,编译器会因为缺少属性而拒绝它.即D中的属性并不是管理内存工具.
就我个人而言,除非写库,否则我一般避免在自己的项中使用属性.你也可用-vgc标志来跟踪GC的用例.
你为什么支持D型垃集器
它也是一个管理内存工具.Joka一般在调试构建中使用跟踪分配器来帮助识别错误,但JokaGcMemory版本则是为偏爱考虑安全的人设计的.该方法与FilC所带的类似.
把Joka和其他D库混在一起难吗?
不.Joka不对代码施加任意限制,因此与d标准库或其他库运行顺畅.有些库选择仅用@安全,@nogc或无抛,但这些是他们的绑定,不是Jokas.
我完全避免用"面向属性"的项目结构方式.
为什么没有jokaFreeSlice函数
因为切片本来就应该像数组而不是指针一样使用.它们在D代码中无处不在,即很容易误解错误的.使用jokaFree(slice.ptr)可避免.
这样可暴露不安全的部分,帮助避免错误.另一个好处是更容易推导.Joka只有一个释放内存的函数.
而Odin语言有三个释放内存的函数:
cpp
free
free_all
delete
如果你是奥丁新手,光凭名字可能很难判断它们的作用.用切片释放内存的方法是删除.使用时如下:
cpp
main :: proc() {
buffer: [256]u8
//在栈上创建一个缓冲.
slice := buffer[:]
//从缓冲取一个切片
//...
delete(slice)
//试`释放内存`.
}
总之,Joka试简单且安全地处理它.
常见的-betterC错误有哪些
使用-betterC按全局@nogc属性.该标志不仅是移除垃集器,还增加了有时可能太限制的额外检查.如果编写无GC代码很重要,且确实需要编译器的帮助,则在每个文件顶部添加@nogc:
或不带i标志使用-betterC.-betterC -i组合一般是可行的.
还有类型信息错误.在源中搜索"新"并删除它.
该目标位使用了struct[N].使用此类类型时需要D运行时的某些部分(_memsetn等),但因为-betterC的工作原理,可能会缺少这些部分.
静态数组的方案是实现缺失的函数,或使用自定义``静态数组类型(joka.types中的StaticArray).
一个基本错误是串错误.一般想在编译时用函数创建串,但因为-betterC标志带来的额外检查,这更难.下面是"正常"生成串的函数,随后是一个与该标志相符的替代方法:
cpp
//`"betterC"`不行.
//参数可来自`运行时`或`编译时`.
string createString(string value) {
return value ~ "_OwO";
}
//同`"betterC"`工作.
//必须在`编译时`已知参数.
string createString(string value)() {
return value ~ "_UwU";
}
Joka的用途
它主要用在游戏引擎``Parin.
为什么要不带GC用D
因为手动管理内存很有趣!