杰基尔2023-11-26T12:28:28+01:00https://orangejuice liberationfront.com/feed.xml橙汁解放阵线Uli关于编程、游戏开发、流行文化和其他无聊事情的博客。TeachText见证人为什么水下水位如此频繁2023-11-26T00:00:00+01:002023-11-26T00:00:00+01:00https://orangejudge liberationfront.com/why-underwater-levels-suck-so-o-quency网站逃亡者放走了一群人,他们的大名鼎鼎,比如本·“亚切”·克罗肖“零标点符号”的名声和来自“Design Delve”的JM8团结一致地退出并开始了新场地“第二次风”。

Design Delve上的最新帖子是关于水下水位的:设计一个不吸人的水下神庙.这让我想起了我对水下部分的不满游戏,以及为什么我喜欢把它们作为可以隐藏可选秘密的临时场所,或者你被明确指示这是接近敌人基地的唯一途径,这是一个不需要太长时间的特殊定位球。

我也喜欢水下部分,如(再次,简短,或被同样长的“上面”打断水”)你进入的区域,找出如何排水,从而从根本上得到两个同一位置的不同视图。

对我来说,以下事情有时会让水位令人讨厌:

水只是一个障碍

在许多游戏中,水上部分的限制比基础游戏严格得多——通常我不能使用武器,有时我甚至无法隐藏。有水流把我推回即使比赛没有风在地面上进行。除此之外,由于必须穿过“厚”水而不是“稀薄”空气。发自内心地,所有这些事情往往会让人感觉像是某人只是想增加难度,没有给我任何其他奖励延长比赛时间。

复杂性增加

根据我们的天性,人类“被栓在地板上”,需要适当的方式(手孔、,梯子、电梯等)进行垂直移动。水域自然是3D的:人类可以利用浮力垂直上升。因此,水域变得更加复杂由于三维导航,而不是仅仅是二维的,由地面决定在哪里你是,而且也许 吧抓钩。敌人也可以从更多的角度攻击我在水下,因为它们可以更容易地在我的上方和下方。

水的物理学

对于人类来说,水是一种不幸的元素。由于质量增加,因此开发人员要么像大多数游戏一样忽略了这一事实,要么以失败告终用来自距离很远,很难判断敌人有多近。相反,水会弯曲光线,所以我看不到远处。此外,如果开发商在模仿自然环境,水应尽一切可能含有微小颗粒物(尤其是在战斗期间,当撞击搅动附近的土壤时),进一步阻碍视线距离并减少可视性灯光。在许多游戏中,接近这意味着所有东西看起来都是蓝色或绿色的,低对比度和模糊,我看不远也看不清楚细节,通常是相当黑暗。

冷却的奴隶

通常需要在水下呼吸空气,这意味着我更容易接受到了特定的冷却时间,最终的结果通常是瞬间死亡。如果我判断错误任何其他冷却,通常只意味着我会损失一些气血或一秒钟内不造成伤害,但在这里,我迄今为止的所有进展都没有实现。这么多放松、探索的游戏突然出现把你放在计时器上,让你变得忙碌起来。

水是一种事后思考

水上部分通常不是游戏的重点,而只是众多关卡中的一个关卡。这意味着许多可以在不同级别之间重新使用的系统必须进行定制。经常会漏掉一些,所以我会遇到一些奇怪的事情,比如角色在下面正常说话播放毫无意义的水和“水上”动画,或者只是动画来避免这种情况,而不是为每个角色创建替换对于一个水位。

以上所有内容

灯光、呼吸时间限制和游戏提示的缺乏也常常使其成为现实很难判断我要去的地方是否时间紧,我必须快点,或者我是否被关门大吉,不可能到达终点/下一个气泡目前。在我可能稍后才跳过该位置。

在大多数游戏中,反复的死亡让我退出了故事,因为这是一个“这不是真的发生了什么-倒带!”这一刻提醒了我这是一场比赛。它也经常感觉就像游戏是在“惩罚”我,因为最后一次扑救需要一段时间在死亡前点重新加载。很少有游戏,例如将你放回更早的保险箱低生命值位置,让您立即继续,而不是触发重新加载。

那么水下水位很差?

不。水下关卡和游戏中的其他关卡一样:一个位置,一个比喻,一个工具在你的工具箱中,你可以利用。它们是添加一些样式和美到一个水平,或使一个水平脱颖而出,或使水平感危险、未知和压迫。然而,正如你上面看到的,他们已经其中存在一些固有的困难。因此,无论何时实施一个,这都是一个好主意考虑你是否真的有时间(如预算)来实施它,以及你如何能够避免上述缺点。

你能让你的角色游得更快吗?你能让他们重新呼吸一下吗空中机修工的无限死亡?你能让你的角色在水平上发表评论吗盖茨,这样球员就不会在没有足够肺的情况下,不必要地尝试下一条长长的隧道容量?你能让水更具风格吗(例如蓝色调+视差气泡?)它仍然像水一样“读”,但不会妨碍细节的可读性?球员是否整个工具箱仍然在水下工作?或者您可以在中修改玩家的工具箱有趣的方式,让它在水下感觉更有趣?

最重要的是:这个位置真的需要在游戏中吗?它是否支持故事和球员能力的发展?球员能把东西传给他们吗学习水下到水面以上的世界?

或者,它应该不是一个完全打开的水位,而是一个突然出现的机械师其他级别的短距离延伸,作为障碍或备用/后退路线,即这是一个更大的挑战,但也很快结束了,人们可以回到他们通常的游戏中在他们开始讨厌水之前?

]]>
尤利维斯
为什么C不是面向对象的语言?2023-07-02T00:00:00+02:002023-07-02T00:00:00+02:00https://orangejuiceliberationfront.com/why-is-c-not-an-object-oriented-language来自“我在堆栈溢出上写下了这个答案”部门:

我可以用C语言编写带有结构和函数指针的面向对象代码。为什么它不被认为是“面向对象的”?

面向对象语言和其他语言之间的区别不是可以以某种方式编写程序(毕竟它们都是图灵完成的),但是否它附带了支持这种编程风格的条款。

最后,程序中的所有内容都会编译成机器代码。顺便说一下意味着您可以在汇编程序中为任何编程范例编写代码。如果你允许的话简化得可怕,C基本上是一个可移植的汇编程序。您可以实现所有编程你可能需要借助宏来完成一些事情,但它可能不像像汇编程序一样高效,可能需要更多的手工工作,但你可以做到。

您需要重新实施许多面向对象编程的规定如果您使用C来代替,则:

您需要手动定义每个实现函数,即虚拟方法为每个类和子类分派表,使它们保持同步并在之间进行广泛的类型转换基类和继承类(或者只知道哪些方法来自基类并通过超级的成员或链超级.超级.超级).

另外,C++知道什么是对象,并且可以检测C编译器的无意义代码看着你对每件事的重新实施,你是无法判断的。它不会只是NULL—初始化vtable末尾缺少的函数指针并继续,它将告诉您,您忘记实现纯虚拟方法,或者将在其自身中填充它因为它看到它被添加到基类中。

此外,面向对象语言构成了面向对象规定的标准。您的OO-with-C-code将不同于其他人的。如果它是内置于语言中的,您可以其他人的OO代码并使用它。如果您使用不同的样式来实现OO-with-C,您可能会创建适配器对象,甚至对于字符串对象或智能指针这样的简单对象也是如此。

]]>
尤利维斯
我的流媒体设置20232023-03-27T00:00:00+02:002023-03-27T00:00:00+02:00https://orangejuice-liberationfront.com/my-streaming-setup-2023考虑到这个网站主要是我写东西的地方,所以我不必记住它,但它也可能对其他人有用,下面是我当前Twitch流媒体设置的简短介绍。

首先,我是什么流动,我想要什么功能?

  1. 我目前主要流媒体播放电脑游戏。因此,为了方便和安全起见,我在我的PC上进行捕获,在那里我可以告诉它只捕获特定的游戏或窗口,而不会冒流密码对话框或包含敏感数据的聊天消息通知的风险。
  2. 我想要音频回避。也就是说,我希望在对着麦克风说话时降低游戏音频的音量,但希望在安静时达到完整的游戏音量。我玩故事情节丰富的游戏,所以人们能够听到游戏音频很重要,我不只是想像一些流媒体那样把游戏音频音量调低。
  3. 我想有覆盖显示当前聊天信息和我的频道徽标,以及新关注者的通知。
  4. 我想在频道中运行一个机器人,我或观众可以用它来播放声音效果。

设置

基本设置

  1. 操作系统流媒体软件。
  2. 拖缆.bot机器人软件。
  3. 我旧手机上的有线入耳式耳机(带插孔)。
  4. 带电容心形话筒的USB/XLR适配器。
  5. 游戏PC(+2 TFT显示器)。
  6. 1080p USB网络摄像头和x分割VCam背景删除软件。
  7. 支架上的iPad用于流式监控。
  8. 流甲板用于快速场景切换、静音按钮、声卡和流启动/停止。
  9. Mastodon上流启动通知的IFTTT帐户。

游戏PC具有两个显示器游戏主屏幕、OBS窗口(包括聊天!)全屏显示的第二个屏幕,以及偶尔浏览网页以查找事实或袭击对象的屏幕。它已插入有线以太网所以我不必担心邻居的微波炉或同一栋公寓楼中的其他Wifis的射频干扰(=更稳定、更快的互联网)。

我使用了一个非生产性的Blue Icicle XLR-to-USB适配器,与我的卡侬话筒一起使用,以更低的价格获得更好的音质。您可以从音频经销商那里买到简单的XLR-USB适配器,大约40美元,比全混音器便宜得多。一位来自德国的朋友推荐了Thomann音像店的T-Bone,只要确保你的麦克风说它需要“幻像电源”,你就可以得到一个可以提供“幻象电源”的适配器,你就没事了。

我的麦克风支架是一个可调节的手臂,夹在我的桌子上。它用一根绳子将麦克风悬挂起来,使其免受我撞桌子时产生的震动。如果你买不起,那就买一个放在地板上的支架,或者至少把麦克风放在你桌子前面的书架上。不要用只放在桌子上的桌子支架,人们每按一次键都会听到一声巨响。

躲避

OBS的“压缩机”过滤器支持“侧链下降”。这意味着你添加了一个压缩机筛选到您的桌面音频点击它的小齿轮图标,将它的“侧链/隐藏源”音频设备设置到你的麦克风上,当你对着麦克风讲话时,它会变得更安静。我还将其“释放”持续时间增加到500毫秒(这控制了你开始讲话后音频恢复到最大音量的速度,即“攻击”)。我的比率是32:1(最大可能),我的门槛-35分贝(略低于我说话的平均音量)。您可能希望根据麦克风和游戏音频的音量大小,使用比率或阈值进行一些调整。

编码器设置

由于我的互联网连接上游不是很好,而且我希望我的YouTube档案质量好,我已经将流媒体PC设置为记录在以下位置使用GPU编码器质量1080p,VBR 4000/5000 kbps,使用nvEnc。对于流媒体,我使用CPU编码器快速的720p和1100 kbps CBR时的x264质量。

这意味着我的PC需要足够强大,能够进行两次编码,并将1080p缩小到720p以用于流媒体运行游戏。为了帮助实现这一点,我让一个编码器在GPU上运行,另一个在CPU上运行。如果你没有一台电脑可以做到这一点,但你有另一台旧电脑或笔记本电脑,你应该看看我的旧2-PC设置和一个捕获设备。

音频监控

为了能够在耳机中听到我自己的声音(能够判断我何时静音或离麦克风太远),我将系统音频输出设置为我的显示器(它没有内置扬声器,但我没有使用耳机端口,所以发送到它的音频实际上听不见)。这是一种低技术的“虚拟音频电缆”。OBS Desktop Audio Capture从那里录制音频。

然后我将耳机设置为OBS设置中的“监听设备”。现在我可以使用OBS主窗口中每个声道上的高级设置,也可以通过显示器播放桌面音频等,所以我听到了游戏。此外,我还可以让某些声音(比如来自机器人的声音,告诉我新的聊天信息)直接进入耳机,这样它们就不会被录制为桌面音频,观众也不会听到。

网络摄像头

我使用一个便宜的1080p USB网络摄像头。它连接在一个灵活的电话臂上,夹在我打折时买的桌子上,所以我可以调整它,使它比显示屏靠得更远。这样,捕获的帧就足够大了,所以当我做大手势时,我的手臂不会被切断,而且我不必注意停留在帧内。

我使用xSplit VCam软件来删除我身后的背景。这意味着我不需要身后的实际绿屏,而且我身后的房间也无法覆盖游戏的一半。尽管如此,xSplit在繁忙的背景下仍然不能很好地工作,所以我确保身后有一堵相当朴素的墙。它完成了任务,这意味着我不必太担心在背景中留下我的地址或其他什么。虽然我计划最终回到绿屏,但这要等到我搬到一个新的地方,在那里我可以进行适当的修改。

套印格式

我在OBS中设置了一系列不同的场景。比如“流开始时倒计时”,“流结束时”显示下一个流的信息,当我只聊天时“会说话的头部”场景等等。我使用了一些技巧来构建这些场景:

  • 我使用“场景”覆盖类型在多个场景中复制相同的覆盖组合。例如,我有一个“游戏”场景,我的“主”场景和“结束”场景使用“场景”覆盖嵌入了该场景。这样,无论我是否使用游戏捕获窗口捕获显示捕获捕捉游戏,或者如果我在一个4:3大小的旧游戏周围添加背景,所有显示游戏的场景都会自动更新。
  • 我使用苹果的Keynote演示软件(类似PowerPoint)来制作小动画和电影,例如浏览即将到来的游戏。我只是将它们导出为MP4电影。
  • 我已经安装了VLC电影播放器应用程序。这将激活OBS中的“VLC播放列表”覆盖类型,可以从文件夹中随机播放电影。我的“流启动”和“中断”屏幕播放我保存到电脑中的剪辑。这样我就不会收到别人偶然拍下的片段,但我不在的时候,人们可以看一些有趣的东西。请注意,所有剪辑的大小都必须相同,否则看起来会出错,所以我将所有剪辑导出为720p。
  • 在我使用绿屏之前,我在网络摄像头上使用了一个黑色圆形蒙版图像PNG在场景覆盖。这样,我得到了一个很好的光滑的圆圈。我还画了一个边框图像,放在网络摄像头上。
  • 对于“会说话的脑袋”场景,我复制了游戏场景(使用网络摄像头游戏),并将所有内容放大,直到网络摄像头占据整个屏幕。这样,它看起来有点像所有东西都被放大了,游戏在我身后依然可见。有一些插件可以让你在场景切换时“放大”动画,但我还没有时间设置。
  • 我有一些文本覆盖显示文件中的文本。我的机器人设置为在特定事件上显示/隐藏特定覆盖,加载随机文本或插入占位符的文本。同样,这些通常使用显示在当前场景顶部的自己的场景。我使用这些来显示“成就解锁”覆盖作为频道积分兑换,或在“流启动”屏幕上显示“有趣的事实”。
  • 我在游戏现场有几份网络摄像头。每个角落都有一个。因此,如果游戏右下角有重要内容,我可以隐藏“WebcamBottomRight”并显示“Webcam BottomLeft”,以确保它没有涵盖任何重要内容。

避免麻烦

一个好的流媒体设置的秘诀是,一旦设置好,就不要过多地摆弄它。因此,我实现可靠设置的主要诀窍是,我不会在流之间进行接触:我将所有USB设备都插在完全相同的端口上,所有设备都尽可能固定在我的桌面上,无法移动。因此,唯一的变量是麦克风距离(当我不使用麦克风时,我将麦克风推回手臂,这意味着我提前10分钟开始流媒体,以便在上线前进行快速麦克风测试),阳光从窗户进来(这对网络摄像头和绿屏有影响,我试图用窗帘挡住一半的窗户,并在第二个可调节的手臂上使用台灯来平衡它们)。

]]>
尤利维斯
OBS中的音频回避2023-02-2600:00:00+01:002023-02-2600:00:00+01:00https://orangejuice liberationfront.com/audio-ducking-in-obs流媒体游戏的难点之一是选择一个与之不同的游戏音频级别声音大得让人无法理解你的评论,但也不会太低,以至于NPC如果观众不不断调整播放音量,对话就听不见。

如果我们的电脑能检测到我说话的时间,并在我说话的时候调高音量就好了安静的!

音频躲避

嗯,它可以。有一种技术叫音频工程师“音频回避”。它可以相反:当你对着麦克风讲话时,它会降低另一个麦克风的音量频道,以确保它不会发出如此大的声音,以至于你再也听不到了。这基本上是与我们想做的相反。

OBS也有这一特点,但它的命名很乏味。它隐藏在“压缩机”过滤器。以下是如何通过3个简单步骤将其用于音频回避:

设置压缩机过滤器

1.打开桌面音频曲目的过滤器窗口

您可以通过查找桌面音频(或任何音频曲目)条目来找到它你想在说话时降低音量),然后单击它的小“齿轮”图标右下角。

这将弹出一个弹出菜单,您可以从中选择“过滤器”以显示过滤器窗口。

2.添加所需过滤器

在该窗口中,单击左下方的小“+”并选择“Compressor”(压缩机)以添加“压缩机”过滤器。

过滤器将显示以下UI来配置它。请注意标有“侧链/躲避源”!我们快到了!

3.配置过滤器

这里最重要的设置是您选择麦克风作为“侧链/躲避源”。这告诉压缩机减少游戏音频响应从麦克风输入。

接下来,我们将“比率”设置得尽可能高(32:1)。这就是比赛的强度音频将减少。如果你想在演讲后听到更多的游戏音频可以将此值减少到1:20或其他值,但通常人们希望将其减少得更多很强,所以这是一个很好的起始值。

如果你的背景噪音很大或说话很轻,你也需要调整“阈值”卷。这应该是一个比任何噪音都高的分贝数(以便散热器打开并发出嗡嗡声不会导致游戏音频降低),但应该也要低于您正常讲话时OBS中混音器显示的分贝值,所以你的演讲实际上会触发它。

我们还将“攻击”设置为1ms(目前可能的最低值为0更好)和“释放”到1秒。这让你一开口就这么做了,小游戏音频将减少。但即使在之后,它也会保持低水平大约1秒,所以如果你是只是在你的句子中稍稍停顿一下,游戏音频就不会响起。

忽略“输出增益”滑块。这基本上是对游戏音频的音量提升,但不是需要躲避。

尝试一下

单击标签为“关闭”的按钮以摆脱“过滤器”窗口并进行尝试。播放放回一些音频,开始说话。看着OBS音频混音器中的电平表,你当你说话时,应能看到桌面音频表的音量明显降低至约三分之一,然后在完成后恢复到最大音量。

祝贺 你!现在可以听到你的声音了,在你安静的时候,游戏中的NPC也可以听到。

]]>
尤利维斯
在MacOS X上交叉编译经典Mac应用程序2022-10-06T00:00:00+02:002022-10-06T00:00:00+02:00https://orangejuice-liberationfront.com/cross-compiling-classic-code-on-osx我喜欢做一些复古编程,但SheepShaver是最好的Mac模拟器,有一个错误,导致复制和粘贴不起作用,所以有点难以使用。我是最近使意识到有一个名为英里/小时模拟的(小写)就够了经典MacOS的在MacOS X上运行苹果MPW编译器套件的命令行工具这是一个尝试和设置。

在OS X上安装MPW

  • 生成这个英里/小时工具根据其中的说明阅读.md,基本上:
    mkdir构建cd内部版本cmake。。制作
  • 获取经典MacOS的MPW副本(他们的获取MPW页面该页面列出了一些地方,如果您没有保留原始E.t.O.或CodeWarrior CD,您仍然可以在这些地方获得它)。
  • 创建文件夹~/mpw.
  • 从中复制文件逐字记录文件夹(例如。~/mpw/环境.text文件,不是~/mpw/逐字/环境.text).
  • 打开文件环境.text使用文本编辑器并更改MPW版本?=3.2行匹配您的任何版本MPW外壳应用程序显示您是否选择显示简介在Finder中显示。
  • 打开接口和库文件夹,并复制接口,图书馆,调试库运行时库其中的文件夹~/mpw文件夹(因此您有例如~/mpw/接口/C包括文件夹)。
  • 打开公共工程部文件夹,并复制工具从那里到的文件夹你的~/mpw文件夹(因此您有例如~/mpw/Tools/AboutBox文件)。

您的MPW现在应该准备好了。

构建MPW附带的标准SillyBalls示例

创建名为的文件夹mpw硅球持有您的项目(您可以在此处选择任何名称,但这就是我今后所说的)。

获取C源代码

查找愚蠢的球。c(c)例子。对于MPW 3.5,它位于MPW/Examples/CExamples/SillyBalls公司。c(c).将其复制到mpw硅球文件夹。

创建资源文件

经典MacOS上的每个应用程序都需要一些资源来描述应用程序和其UI。这些通常在资源文件中定义。您可以在中创建此资源文件一种名为雷兹。我们将创建一个名为愚蠢的球。第页这只是告诉操作系统我们应用程序的功能:

#包括<SysTypes.r>#包括<Types.r>/*这是典型的MultiFinder友好设备,SIZE资源*/资源“大小”(-1){不保存屏幕,接受暂停恢复事件,启用选项开关,canBackground,/*我们可以设置背景;我们目前没有,但我们的睡眠价值*//*保证我们在后台时不会占用Mac电脑*/multiFinderAware,/*这表示我们自己进行激活/停用;不要假装我们出去*/backgroundAndForeground,/*这绝对不是纯背景应用程序*/dontGetFrontClicks,/*如果您希望像Finder那样“先单击”行为,请更改此设置*/ignoreChildDiedEvents,/*本质上,我不是调试器(子启动)*/是32BitCompatible,/*此应用程序可以在32位地址空间中安全运行*/保留,保留,保留,保留,保留,保留,保留,23*1024,/*23kb首选最大RAM*/35*1024/*35kb最小RAM限制*/};

创建生成此应用程序的Makefile

#这应该指向您从该存储库构建“mpw”工具的位置:公共工程部=~/Programming/mpw/build/bin/mpwRINCLUDES公司=~/mpw/接口/R包括#“SILB”是这个傻球应用程序的唯一“创建者代码”,用于关联图标#并告诉Finder使用此应用程序打开文件。弥补#您在这里为您的应用程序提供了自己独特的4字符代码。通常使用所有小写代码#由Apple创建,因此至少使用一个大写字母。LDFLAGS(着陆标志) =-w个 -c(c) “SILB” -t吨应用程序\
	-锡 STDIO公司=主要-锡 意图=主要-锡%A5初始化=主要PPC_LDFLAGS(PPC_LDFLAGS) =-米主要的-w个 -c(c) “SILB” -t吨应用程序图书馆={图书馆}存根。\
	{图书馆}MacRuntime。\
	{图书馆}国际环境。\
	{图书馆}接口。\
	{图书馆}工具库。\
	{C图书馆}标准CLib。PPC_库={共享库}接口库\
	{共享库}标准CLib\
	{PPC库}标准C时间。\
	{PPC库}PPCC运行时。工具箱标记=-d日 旧程序=1-打字检查轻松的来源=愚蠢的球。c(c)物体=$(来源:%。c(c)=目标/%.68k。)
PPC_对象=$(来源:%。c(c)=对象/%.ppc。)

RFILES公司=愚蠢的球。第页可执行=愚蠢的球全部的: prepass bin/$(EXECUTABLE).ppc-bin/$(可执行).68k

预处理:
	mkdir(主目录) -第页对象binbin/$(可执行).ppc: $(PPC_OBJECTS)
	$(MPW)PPC链接$(PPC_LDFLAGS) $(PPC_OBJECTS) $(PPC_LIBRARIES) -o个 $@; \雷兹-第个 $(RFILES) -o个 $@ -我 $(林克吕德斯) -追加

bin/$(可执行).68k: $(对象)
	$(MPW) 链接 $(LDFLAGS) $(对象) $(图书馆) -o个 $@雷兹-第个 $(RFILES) -o个 $@ -我 $(林克吕德斯) -追加

目标/%.68k。: %.c(c)
	$(MPW)联合国安全理事会$(工具箱标记) $< -o个 $@

对象/%.ppc。: %.c(c)
	$(MPW)MrC公司$(工具箱标记) $< -o个 $@; \

清洁的:
	rm(毫米) -射频bin对象

构建应用程序

只需键入制造所有在您的mpw硅球目录。Makefile现在将创建一个垃圾桶/彩球.68k和a垃圾桶/彩球.ppc可执行文件。

与CLion集成

由于我们在现代操作系统上运行,我想如果我能集成编译器,我会尝试一下使用现代IDE。我已经拥有了CLion,并且喜欢它的代码导航和重构功能,所以我想我应该使用CLion的自定义编译器支持.

因此,正如CLion文档所说,我创建了一个编译器定义YAML文件,并使用了CLion窗口右上角的装备弹出菜单的“首选项…”菜单项告诉我有关此编译器定义文件的项目。

它给出错误“找不到编译命令”发现CLion的Makefile解析器可能不够智能,无法识别$(MPW)SC与相同最大功率SC等等。所以我去创造了一个小贝壳脚本南卡罗来纳州要将模拟编译器调用包装为单个命令,请执行以下操作:

#!/箱子/zsh

#需要此脚本,以便CLion IDE识别编译器并运行它。~/Programming/mpw/build/bin/mpw-SC$@

然后更新Makefile以调用它,而不是$(MPW)SC,并更新了YAML提交至最终形式:

编译器:
  - 描述: "公共工程部 SC“
    匹配源: ".*\\.c英寸
    匹配语言: "C”
    match-compiler-exe: "(.*/)?sc.sh“
    代码瞄准目标名称: 68k米
    包括直径:
      - ${user-home}/mpw/接口/CIncludes
    定义-文本: "
#定义 __SC公司__ 0x0801
#定义 MPW_C型 1
#定义 旧程序 1
#定义 帕斯卡语
"

这里的重要部分(除了使用shell脚本)包括:

  1. 匹配源所以它知道这个编译器处理.c类文件夹
  2. 匹配语言告诉CLion的索引器(叮当作响)解析这些内容源为。
  3. 代码瞄准目标名称–我对这一点最不确定。68k米像往常一样早期Mac电脑的摩托罗拉68000系列CPU的缩写,但我找不到目标平台列表叮当作响实际上支持。
  4. 包括直径告诉索引器在哪里查找系统标头。这意味着你can命令-单击窗口指针在源文件中,它将跳转到中的定义MacWindows。小时.
  5. 定义-文本告诉CLion假装编译器已经定义了一些常量。大多数编译器都定义了一些常量,让您可以检测它是哪个编译器clangd基于叮当作响编译器,因此它没有为我们的习俗联合国安全理事会编译器,所以我们必须手动定义那些通常要定义的在这里。
    苹果的条件宏。小时header查看这些常量以设置更多标准化常量,无论您使用什么编译器,因此这是一个很好的地方找出您需要的常量以及它们应该具有的值。
    例如,如果您正在设置PowerPC编译器MrC公司,你可能不得不定义__磁共振__0x0700个而不是。我刚刚选择了标题的最高值对于这个常数。我可能真的应该在模拟器中运行这个编译器看看我的版本的实际价值是什么。我还决定定义帕斯卡语,因为它是一个关键字叮当作响否则将在上显示错误。理想情况下,我们将定义这是一个不寻常的电话会议所以叮当作响如果我们传递一个Pascal函数通过与C调用约定不匹配的函数指针,但我还没有抽出时间做这些。此外,我试图定义类型_ BOOL1保持条件宏。小时来自重新定义真的,但它只是为每个编译器硬编码该值,所以我找不到方法只覆盖它对于CLion叮当作响索引器。

无论如何,这让CLion感到高兴,不仅我现在能够跳转到系统标题使用CLion的标准导航,它现在还允许我使用小“Hammer”图标用构建我的应用程序制造所有、和生成>清洁要运行的菜单项使干净.

现在还要为C++编译器设置一些东西,然后我就可以进行编码了。

PS-上面的一些描述可能看起来像“mpw”`项目的Wiki。这是因为关于如何初始设置的文档很少工具,所以我为他们的Wiki贡献了这篇文章的早期草稿。]]>
尤利维斯
C++的std::variant惊人的缓慢2020-11-28T00:00:00+01:002020-11-28T00:00:00+01:00https://orangejuice liberationfront.com/surrising-slowness-of-cpp-std-variant我在业余时间学习自己的脚本语言。在这种语言中,脚本程序不指定变量的类型,变量根据脚本程序输入的内容更改类型他们。

这就是通常所说的变体.

C++自带变体类,但它用于存储完全不同的类型。假设我真的不想关心脚本编写器使用了什么类型,并执行隐式转换时,我实际上可以将其实现为一个类层次结构,使用一个公共的基类如:

类值{公众:~Value()=默认值;int64_t GetAsInt64()常量=0;无效SetAsInt64(int64_t)=0;字符串GetAsString()常量=0;void SetAsString(常量字符串&)=0;};

然后子类如下

类Int64Value:公共值{公众:值(int64_tn=0):mValue(n){}int64_t GetAsInt64()常量{return mValue;}无效设置为int64(int64_t n){mValue=n;}字符串GetAsString()常量{return to _string(mValue);}void SetAsString(常量字符串&n){mValue=环礁(n);}受保护的:int64_t mValue值;};

现在的问题是,这为所有类型提供了一个通用接口我们对它们的处理是一样的,但我们必须知道编译时变量的类型。我们还没有设法制造变量更改类型。要做到这一点,你必须使用操作人员新的:你会的删除新的它在一个新类型下改变其类型。但这很糟糕,因为它会在堆上为每个整数/一串您希望使用。性能将慢得离谱。理想情况下,我们希望与任何其他类型一样,这些都要内嵌(或在堆栈上)。

通常,您会使用联盟为此:

工会ValueUnion{int64_t mInteger;字符串mString;};

但联合不喜欢具有类似构造函数的类型一串,因为它不能保持跟踪工会的当前类型,因此不知道是否必须调用~字符串()或者没有。理论上你会使用变体(但我会在下面提到为什么这在这里不起作用)。

那么我们该怎么办?我不知道,直到有一天我想起C++有一个叫做安置新的.放置新的让您提供存储,然后将调用对象的构造函数。它适用于以下类矢量,用于分配为所有元素创建一个大块,然后将对象放在数组中其他。所以我添加了以下类:

联合变量联合{变量Union(){}~VariantUnion(){}Int64Value mInteger;StringValue mString;};class alignas(union VariantUnion)VariantValue:公共值{公众:变量值(int64_t n=0){新(mStorage)Int64Value(n);}变量值(常量字符串&n){new(mStorage)StringValue(n);}~VariantValue(){((值*)存储)->~Value(;}int64_t GetAsInt64()常量{return((值*)mStorage)->GetAsInt54();}void SetAsInt64(int64_t n){((值*)mStorage)->SetAsInt64(n);}...受保护的:uint8_t mStorage[sizeof(ValueUnion)];	};

因此,基本上它的唯一工作是将所有调用转发到底层对象。它还将使用放置创建基础对象新的(并使用放置再次破坏它删除,这看起来像是直接调用析构函数)。我们必须提供存储自己(我们通过确保m存储足够容纳这是我们想要的,因为我们可以只声明我们的存储作为固定大小的字节数组内嵌,并放弃额外的分配。

那么我们如何才能更改类型呢?我们需要摧毁水流价值在mStorage中创建子类并使用placement分配一个新的新的。我在通过实现所有其他类型的setter子类:

类Int64Value:公共值{公众:...void SetAsString(常量字符串&n){((值*)this)->~Value();新(此)StringVariantValue(n);}...};

这样,如果变量已经是整数64_t,它会改变的Int64值::mValue, 但如果它是另一种类型,它将在适当的位置重新分配该类型。

我知道这个代码很可怕:你需要确保价值联盟包含您的所有支持的类型,否则可能会超载内存并导致难以发现的错误。还需要确保使用正确的对齐方式,因为单位8_t通常按1字节边界对齐,这对于例如整数64_t对许多人平台。由于我们正在覆盖对齐,因此您不能有任何其他成员变量变量值没有考虑这是否会错位m存储。它还涉及Int64值在其方法运行时销毁自身并在其位置上构建一个新对象。最后,Int64值做出假设它的包含类分配给它的存储空间有多大。这不是正确封装。

不过,从好的方面来看,这个类的用法非常简单。你只是呼叫设置为字符串()获取字符串()它会神奇地做出正确的事情,或者如果无法转换,则抛出异常。

那么性能是什么样的?

在一次快速测试中,我使用了数组<int64_t>作为基线,运行1000万个循环用我的编程语言进行迭代。这花了10毫秒。

使用上述方法,当然可以用函数代替直接内存访问每个调用和一点虚拟调度开销获取AsInt64()设置为Int64()调用时,我的Mac电脑的运行时间增加了一倍,达到20毫秒,而Windows的运行时间则增加了大约30毫秒。

然后我尝试使用C++变体:

内联字符串GetAsString()常量{返回std::visit([](auto&&arg){使用T=std::decade_T<decltype(arg)>;如果constexpr(标准::is_same_v<T,int64_T>){返回字符串(arg);}else if constexpr(std::is_same_v<T,string>){返回参数;}其他{static_assert(always_false_v<T>,“非穷尽访客!”);}},mValue);}inline void SetAsString(常量字符串&n){m值=n;}...变量<int64_t,string>mValue;...

这花了惊人的100毫秒。考虑到这涉及到许多新奇的构造,比如lambdas和泛型,我尝试去更多的老学校,通过变量::index()用于比较:

内联字符串GetString()常量{开关(mValue.index()){案例0:返回字符串(获取<int64_t>(mValue));断裂;案例1:return get<string>(mValue);断裂;}}

这实际上使我们下降到60毫秒。但它也让我们失去了编译时类型的安全性如果有人在变量<int64_t,字符串_>的类型列表,而不是在末尾。它也不会如果我们忘记实现像纯虚拟方法那样的类型,就会引发任何错误怀疑这将对每个赋值的赋值执行额外的类型检查,而我们的多态方法知道什么时候类型不会改变,只会通过虚拟调度,这可能更适合缓存。

因此,对于多态性方法来说,最坏的情况是速度下降了3个因素,而速度下降了6到10个因素C++内置的速度减慢变体另外,给定变体假设完全独立类型,使用特定类型访问变量的代码(并执行适当的转换)使用多态性更干净。

那么C++的标准变量坏吗?

我不会发表那种笼统的声明。我还没有多少上课经验,所以我可能只是没有使用正确的调用,我有一个非常具体的用例与什么不太匹配变体是为了支持。我只能说我鼓励你不能只是坚持变体进入核心解释器循环,而不将其与其他方法-第页

]]>
尤利维斯
我的流媒体设置20202020-04-26T00:00:00+02:002020-04-26T00:00:00+02:00https://orangejuice liberationfront.com/my-streaming-setup-2020考虑到这个网站主要是我写东西的地方,所以我不必记住它,但它也可能对其他人有用,下面是我当前Twitch流媒体设置的简短介绍。

首先,我是什么流动,我想要什么功能?

  1. 当前的流媒体PS4和PC游戏。我想以高质量完成这项工作,所以我选择了一个带有单独流式PC设置的捕获设备。
  2. 我想要音频回避。也就是说,我希望在对着麦克风说话时降低游戏音频的音量,但希望在安静时达到完整的游戏音量。太多的流都有很低的音频,因此不会与他们的评论相冲突,这使得很难理解故事。
  3. 我想有覆盖显示当前聊天信息和我的频道徽标,以及新关注者的通知。
  4. 我想在频道中运行一个机器人,我或观众可以用它来播放声音效果。

为什么PS4没有内置流媒体?

PS4内置流媒体非常棒:

  1. 你只需按下控制器上的一个按钮,就会得到一个启动/停止流的菜单。
  2. 它在屏幕右边缘显示聊天内容。
  3. PS4为流媒体预留了容量,因此即使没有额外的PC和硬件捕获卡,也不会出现断断续续或速度减慢的情况。
  4. 我只需插入一个USB耳机,麦克风将用于评论,耳机将播放游戏音频,而不会被耳机麦克风拾取。
  5. 聊天通知和其他私人数据会自动模糊,关闭游戏会关闭流视频,所以我不必担心意外显示好友列表、PSN ID或包含敏感数据的私人消息。

遗憾的是,它有几个缺点:

  1. 聊天覆盖中的文字环绕非常可怕。我认为他们只是使用日语风格的字符包装,而不是只在单词之间的空格处中断。
  2. 聊天覆盖并没有显示所有的Twitch表情,只显示了一组较旧的表情。
  3. 在没有额外PC的情况下,无法访问Twitch的网站来检查流的声音或是否存在压缩伪影。
  4. 不支持机器人程序。我仍然需要为聊天机器人程序运行PC。
  5. 无游戏音频回避。

设置

基本设置

  1. Elgato游戏捕获高清
  2. 操作系统
  3. Elgato声音捕捉
  4. 微软LifeChat LX-3000耳机(带麦克风)
  5. 流媒体PC游戏捕获软件,机器人。
  6. 游戏PC。实际游戏的PS4(+TFT显示屏)

我将耳机插入流动PC,并设置OBS,使其记录来自麦克风的音频。

我在游戏PC和显示器之间插入Elgato Game Capture HD(简称EGC)。来自EGC的USB导线进入将运行OBS的流式PC。

如果你有一个当前的EGC设备,你只需在OBS中的“高级音频设置”下打开监控,就可以听到耳机中正在录制的游戏音频。

具有延迟的旧捕获设备

考虑到我的EGC是一个有延迟的旧模型,与我在屏幕上看到的相比,它来得晚得令人困惑。因此,我在游戏PC上使用内置于Windows中的“立体声混音”设备,不仅将音频输出到显示器(因此它可以进入EGC),还可以输出到线路输出端口。这是一个默认禁用的设备,必须在Windows的旧“声音控制面板”中打开(首先显示非活动和禁用设备,然后启用StereoMix设备)。音频通过插孔对插孔电缆和a带线内音量控制器的插孔延长电缆进入麦克风输入流式PC的端口,然后我设置麦克风输入端口,通过“收听此设备”通过声音控制面板中的耳机输出,而流式电脑的默认音频正在播放Elgato声音捕捉。这使我可以将所有想要录制的音频(即机器人音频)输入到Sound Capture中,我仍然可以在耳机中听到这些声音,但我不想录制的声音(来自麦克风输入)只会出现在我的耳机中,绕过了声音捕捉,从而避开了OBS。

为了捕获PS4,我使用了一个称为“HDMI音频提取器”的小硬件加密狗来实现相同的功能。它插在PS4和显示器之间,就像EGC一样。但同样,只有当你有一个有延迟的捕获设备时才需要,而Elgato的新设备没有延迟。

躲避

OBS的“压缩机”过滤器支持“侧链下降”。这意味着你添加了一个压缩机筛选到您的EGC公司点击它的小齿轮图标,将它的“侧链/隐藏源”音频设备设置到你的麦克风上,当你对着麦克风讲话时,它会变得更安静。我还将其“释放”持续时间增加到500毫秒(这控制了你开始讲话后音频恢复到最大音量的速度,即“攻击”)。我的比率是32:1,我的门槛-35分贝。您可能希望根据麦克风和游戏音频的音量大小,使用比率或阈值进行一些调整。

编码器设置

由于我的互联网连接上游不是很好,而且我希望我的YouTube档案质量好,我已经将流媒体PC设置为记录在以下位置使用GPU编码器质量1080p,VBR 4000/5000 kbps,使用nvEnc。对于流媒体,我使用CPU编码器快速的720p和1100 kbps CBR时的x264质量。

由于游戏PC上运行的是CPU和GPU密集型游戏,因此流媒体PC可以进行两次编码,并将流媒体压缩到1080p到720p,而不会对游戏性能产生不良影响。由于一个编码器在GPU上运行,另一个在CPU上运行,因此两个编码器都有足够的处理能力,而不需要一个从另一个中取走(如果您没有nVidia GPU,则可以使用Intel QSV而不是nvEnc)。

机器人音频

Elgato Game Capture for Windows附带一个名为“虚拟音频电缆”的应用程序Elgato声音捕捉它有几个模式可以运行。我使用它的“音乐”模式来获取聊天机器人在流媒体PC上播放的音频并进行OBS录制(因为EGC只包含PS4/游戏PC音频)。

为此,我将Sound Capture的输出设备设置为默认的系统输出设备,然后告诉Sound Capture通过耳机播放。然后我设置OBS从Elgato Sound capture设备捕获桌面音频。这样,我就可以听到机器人的音频,因为Streamlabs聊天机器人在默认的音频输出设备上播放所有声音,但它也会被录制下来。

OTOH,我设置为直接通过耳机播放的任何其他音频(如上面旧捕获设备的未播放游戏音频)都不会被录制。

其他设置杂项

PS4/游戏PC和流式PC均接入有线互联网,以避免Wifi问题,并获得全千兆速度的流式上传。

我还有一个Blue Icicle卡侬到USB适配器,可以与卡侬话筒配合使用,以获得更好的音质,但我更喜欢耳机,因为这意味着我可以转头查看噪音或时钟,仍然可以听到。如果我再次使用网络摄像头流(我有一个Logitech C270),我可能会放弃耳机,在单独的麦克风支架上使用麦克风,然后再使用iPhone耳机,这在摄像头上不太明显,但我不想使用它的麦克风,因为我经常用下巴撞击麦克风。

我的麦克风支架是你可以站在地板上的全高支架之一,因为桌子支架会接收我在桌子上打字时产生的噪音和振动。

免责声明

Elgato Game Capture HD是我在Elgato工作时收到的礼物(我于2017年年中离开)。我曾经在Mac版本的Elgato Game Capture上工作。这就是说,对于从PS4、覆盖层和所有内容进行拍摄,拍摄设备是唯一的选择,所以我认为即使我有偏见,我也不会推荐任何其他我不会推荐的东西。

]]>
尤利维斯
C中的X宏2020-02-22T00:00:00+01:002020-02-22T00:00:00+01:00https://orangejuiceliberationfront.com/x-macros-in-c什么是X宏?

C语言有一个巧妙的特性,可以用来克服它的许多缺点:C预处理器,它基本上是一种定义复制和粘贴文本位的指令的方法。

X-Macros使用此功能可以定义列表,然后基于同一列表生成几位代码。

您可以这样做,因为预处理器允许您使用#未定义预处理器命令,以便您可以使用不同的展开形式再次定义它们。让我们做一个例子:

#定义颜色X(红色)\X(绿色)\X(蓝色)
//定义枚举:
#定义X(name)COLOR_##名称,
枚举 颜色 {
	颜色
	颜色_数量
};

#未定义X
//定义字符串表:
#定义X(名称)#name,
常数 烧焦 * g颜色名称[颜色_数量 + 1] = {
	颜色
	""
};

#未定义X

预处理器运行后,该代码将如下所示:

枚举 颜色 {
    颜色_红色, 颜色_绿色, 颜色_蓝色,
    颜色_数量
};

常数 烧焦 * g颜色名称[颜色_数量 + 1] = {
    “红色”, “绿色”, “蓝色”,
    ""
};

允许您打印枚举颜色值如:

枚举 颜色 我的颜色 = 绿色;
打印(“myColor=%s\n个", g颜色名称[我的颜色]);

并获得打印输出:

myColor=绿色

由于该技术的初始示例命名了要替换的宏X()(就像我们在上面所做的那样),该技术也被命名为该技术,并且它一直作为一种约定而存在,尽管如果您愿意,您当然可以选择更具表现力和自我记录的名称。

为什么要使用X-Macros?

当您处理需要保持同步。无论是像上面这样的两个实际列表,还是一个列表和一个switch语句,都需要遵循一个简单的模式来涵盖其所有情况。令牌映射操作符##和字符串运算符#在许多这样的情况下非常方便,可以让您获取相同的数据并将其用作不同的数据类型。

包含的X宏

经典X-Macros的一个缺点是,如果宏中不能有换行符,那么所有常量都会出现在同一行上。定义颜色上面只转义了换行符,这意味着它们不是宏输出的一部分,它们只是将定义拆分为多行以使其更具可读性。更糟糕的是,大多数IDE将跳转到颜色中的行枚举而非实际定义颜色_红色条目,当您使用他们的代码导航功能时。这使得很难找到您可能在单个X宏旁边编写的单个枚举案例的文档。

解决方法之一是不为颜色列表,并将其写入单独的文件:

颜色_X.h

X(X)(红色)
X(X)(绿色)
X(X)(蓝色)

而是(ab)使用#包括要将标题粘贴两次:

//定义枚举:
#定义X(name)COLOR_##名称,
枚举 颜色 {
#包括 “颜色_X.h”
	颜色_数量
};

#未定义X
//定义字符串表:
#定义X(名称)#name,
常数 烧焦 * g颜色名称[颜色_数量 + 1] = {
#包括 “颜色_X.h”
	""
};

#未定义X

每个X宏现在都在自己的行上,从而产生预处理代码:

枚举 颜色 {
    颜色_红色,
    颜色_绿色,
    颜色_蓝色,
    颜色_数量
};

常数 烧焦 * g颜色名称[颜色_数量 + 1] = {
    “红色”,
    “绿色”,
    “蓝色”,
    ""
};

此外,编译器会跟踪包含的文件中的行号,以及当您在颜色_红色标识符,它现在将跳转到X(红色)线路输入颜色_X.h,根据需要。

当然,枚举和字符串数组定义现在看起来更不可读,并且您需要为每个枚举创建一个单独的文件。

我应该在代码中使用X-Macros吗?

一般来说,我会避免使用X-Macros。我用这种技术写了一篇完整的博客文章,这应该说明它们并不明显,而我上面给出的警告应该告诉你,X-Macros有自己的问题。这就是说,如果您有一个由许多需要保持同步的静态查找表组成的C代码库,那么X-Macros可能是最不可怕的解决方案。如果有不同步的表可能导致难以诊断的错误,那么通过评论对每位新员工进行一点关于X-Macros的教育是一个很小的代价。所以,如果不能使用静态声明的结构,请在那里使用它们。

毕竟,如果你已经被迫使用C而不是更高级别的编程语言,那么你可能已经受到了很多限制,而X-Macros实际上可能会导致更可读、更可维护的代码。

]]>
尤利维斯
依赖关系管理器如何工作?2020-02-08T00:00:00+01:002020-02-08T00:00:00+01:00https://orangejuries liberationfront.com/how-do-dependency-managers-work(在线阅读)像CocoaPods、Carthage或SwiftPM这样的依赖管理器似乎很神奇。但他们似乎也有点尴尬和复杂。让我们把它们分解成各自的组成部分让人们更容易理解他们为什么以这种方式工作。

获取(匹配)代码

依赖项管理器必须做的第一件事是确保获得正确的版本应用程序当前版本的代码。最简单的方法是Git子模块:您定义依赖项代码的某个版本(在本例中,特定Git提交)并将其写入应用程序的当前版本。然后,当如果你签出你的应用程序,那么代码也会被签出。

这里最重要的部分是依赖项的代码最终位于固定目录路径,所以您的Makefile或Xcode项目文件或其他任何文件都可以可靠地引用它。

这两点都由您的.git模块file:指定目录应该签出依赖项,以及表示代码的特定版本。每个依赖项管理器都有大致相同的东西,它叫什么名字购物车文件,播客文件,或包装.swift.

合并重复依赖项

依赖项管理器需要做的第二件事是,如果存在多个依赖关系依赖于同一个子依赖关系,您只能得到一个子依赖。想象一下你的应用程序打电话我的音乐播放器.app它由两个子组件组成,MP3回放库AAC回放库。这两个依赖关系都依赖于元数据库从属关系用于从文件中读取艺术家、歌曲标题、作曲家、专辑等。

你最初的直觉是添加元数据库二者的子模块MP3回放库AAC回放库。但你会收到编译器的投诉关于重复符号。

因此,大多数依赖关系管理器在这种情况下所做的是,它们强制执行相同的操作依赖项只下载一次,并由所有需要它的子模块共享通常情况下,它们不会将依赖项下载到与依赖于它的组件,但位于中心位置。就可可荚而言意味着所有依赖项都下载到豆荚应用程序文件夹中的目录。迦太基有自己的迦太基文件夹,SwiftPM将所有依赖项下载到其包装目录。

这不仅使依赖关系管理器更容易检测重复的依赖关系,这也意味着依赖项知道它们的其他依赖项将紧邻它们在文件系统中。事实上,我从事的项目就是这样处理的依赖项:每个应用程序都有一个子模块文件夹,我们将所有依赖关系。所有子模块(框架、库等)都已设置,因此预期他们的依赖性就在他们旁边,这是应用程序存储库的工作正确的版本,以便所有内容都能构建。

包管理器只是自动完成这项工作。

解决冲突

依赖项管理器必须做的另一件事是检测模块之间的冲突,如果可能的话,解决它们。他们如何做到这一点,是通过允许一系列版本,而不是只有一个固定版本。这样,如果您只是进行兼容更改,依赖项管理员可以自动将您升级到新版本,使其与其他版本相匹配组件的要求。

他们通常通过收集每个模块的所有需求,然后匹配针对这些需求的可用版本,通常选择最新的匹配版本。通常这也意味着您不能像子模块那样只引用Git分支,而是需要实际标记发布并使用语义版本控制能够区分彼此兼容的版本和不兼容的版本。

这样做的优点是依赖关系管理器可以查看子组件是否具有依赖项的版本要求冲突。比如,如果MP3回放库习惯于元数据库 1.0.0但是AAC回放库欲望元数据库 2.0.0,其中增加第一个数字根据语义版本控制表示API已以不兼容的方式更改,没有任何版本与中指定的版本范围播客文件/购物车文件/包装.swift,因为范围是1.x.x对于MP3回放库但是AAC回放库的范围是2.x.x版,什么都不能二者都1.x个2.x个.

已解析的依赖项列表文件

大多数依赖项管理器都有列出依赖项的文件的第二个版本。CocoaPods有播客文件.lock,迦太基Cartfile.已解决SwiftPM有已解决的包.这些是干什么用的?

普通依赖项列表文件(播客文件,购物车文件,包装.swift)包含一个范围版本号的。但你不能建立“一系列版本”。你必须选择一个。我们希望能够返回到应用程序的特定版本并重新构建它(例如。要修复错误),我们需要能够记住(在Git或任何版本控制中using)当时我们的依赖范围被解析为什么版本。

这就是解析的依赖项列表文件的目的。它包含具体版本的依赖项,并且您可以将其签入到您的版本中控件。

将依赖项集成到构建中

虽然严格来说不是依赖管理的一部分(例如,迦太基没有这样做),许多依赖项管理器也与应用程序集成,以避免您必须引用他们在应用程序中检出的依赖项。

虽然苹果刚刚将SwiftPM支持添加到Xcode中,但依赖项管理器没有由第一方维护的必须在这里做自己的工作。就可可荚而言,这个一开始有点困惑,但实际上并没有那么复杂。

CocoaPods所做的是,它基本上编辑您的项目文件并添加依赖项假设它们都是框架和库。然而,自从CocoaPods和开发人员编辑项目文件都有点风险,他们实际上创建了一个围绕项目工作。工作区是围绕几个项目的保护伞让它们相互看到,并允许您手动或通过覆盖其中的设置xcconfig文本文件。

因此,CocoaPods所做的是,它围绕您的项目创建一个工作区。唯一一个编辑工作区是CocoaPods。您可以自由编辑项目文件,无需任何碰撞。从那以后,你只需要记住双击“工作区”不是您的项目来构建您的应用程序(否则您将无法获得依赖项和应用程序无法构建)。

就这样?

对。您知道,依赖项管理器的一般功能由几个简单的部分。当然,创建自己的并使其可靠地工作更难,而且可能需要的大量边缘案例和附加功能。但它的核心是什么?只是对于一个人们已经用手解决了很久的问题,一个相当直接的解决方案一会儿。

你知道这是怎么回事:不要花时间解决你没有的问题,但一旦你有一个问题,有一个自动化的解决方案意味着你可以避免偶然的错误用手表演。

]]>
尤利维斯
锁锁什么?2020-02-05T00:00:00+01:002020-02-05T00:00:00+01:00https://orangejuries liberationfront.com/what-do-locks-locks-lock我刚读了一篇有趣的问题:

当调用.lock()时,内存的哪一部分会被锁锁住?只是因为函数还是整个程序被锁定了?

我认为这将是一个有趣的回答。让我们从一个有争议的标题开始:

锁实际上什么都锁不上

是的,你没听错!锁是一种可以在代码中使用的工具,用于您的代码知道何时可以修改变量。这就是它的全部功能。它不会“锁定”内存”或“锁定功能”。它基本上像一种特殊的布尔值这说明某些东西是否可以修改。

这并不是锁的实际实现方式,但让我们暂时将锁想象为只是一个包装布尔值:

类锁{var okToModify=真函数锁定(){与此同时!okToModify{//另一个线程锁定了它吗?//等待,直到另一个线程解除锁定。}okToModify=false}函数解锁(){okToModify=真}}

现在,如果我们想保护一个int不被几个线程访问,我们会将其声明为:

var myInt=0让myIntLock=锁定()

无论我们想在哪里访问myInt,我们都会:

myIntLock.lock()myInt+=1myIntLock.unlock()

当然,上面的代码是胡说八道。如果另一个线程可以通过修改我的Int+= 1发生时,它更容易通过介入而引发问题while(!okToModify)okToModify=false在中锁定()功能以上。

为什么?因为+=1实际上是3条机器指令:

  1. 阅读我的Int
  2. 添加1我们刚刚读到的
  3. 将结果写回我的Int

这就是为什么锁类实际上使用特殊的魔法机器指令在一次操作中进行比较和更改。没有其他线程可以更改确定要修改变量,而另一个变量已经在做了。它只是编译器魔法。

所以,虽然布尔值不能用作锁,锁是布尔值,用最简单的方式使用锁。例如,在Swift中,有一个特殊对象称为调度信号量可以用来实现锁的。这个信号灯为你带来所有魔力:

类锁{let信号量=DispatchSemaphore(值:1)函数锁定(){信号量.wait()}函数解锁(){信号量.signal()}}

整洁,嗯?

]]>
尤利维斯