哈希表

哈希表允许您实现关联数组(字典)数据结构,插入、删除和搜索操作的平均性能为 O(1)。

下面是 NodeJS 中哈希映射的最简单实现的示例:

它是如何工作的?小心你的手:

  • 哈希映射内部有一个数组
  • 数组元素内部有一个指向链表第一个节点的指针
  • 内存分配给指针数组(例如,65535 个元素)
  • 它们实现了一个哈希函数,字典键是输入,在输出它可以做任何事情,但最终它返回数组元素的索引

录音的工作原理:

  • 入口处有一对钥匙–值
  • 哈希函数按键返回索引
  • 通过索引从数组中获取链表节点
  • 检查是否与key匹配
  • 如果匹配,则替换该值
  • 如果不匹配,则转到下一个节点,直到找到或找到具有所需键的节点。
  • 如果仍未找到该节点,则在链表末尾创建该节点

按键搜索的工作原理:

  • 入口处有一对钥匙–值
  • 哈希函数按键返回索引
  • 通过索引从数组中获取链表节点
  • 检查是否与key匹配
  • 如果匹配,则返回值
  • 如果不匹配,则转到下一个节点,直到找到或找到具有所需键的节点。

为什么我们在数组中需要一个链表?由于计算哈希函数时可能发生冲突。在这种情况下,多个不同的键值对将位于数组中的同一索引处,此时将遍历链表来查找所需的键。

来源

https://ru.wikipedia.org/wiki/哈希表
https://www.youtube.com/watch?v=wg8hZxMRwcw

源代码

https://gitlab.com/demensdeum/datastructs

在 Android C++ 中使用资源

通过 ndk 使用 Android 中的资源 – C++有几种选择:

  1. 使用 AssetManager 访问 apk 文件中的资源
  2. 从互联网下载资源并将其解压到应用程序目录中,使用标准 C++ 方法使用它们
  3. 组合方法–通过AssetManager访问apk中资源的存档,将它们解压到应用程序目录中,然后使用标准C++方法使用它们

接下来我将描述Flame Steel Engine游戏引擎中使用的组合访问方法。
使用 SDL 时,您可以简化对 apk 资源的访问;该库包装了对 AssetManager 的调用,提供类似于 stdio 的接口(fopen、fread、fclose 等)

SDL_RWops *io = SDL_RWFromFile("files.fschest", "r");

将apk文件下载到缓冲区后,需要将当前工作目录更改为应用程序目录,应用程序可以使用该目录,而无需获得额外的权限。为此,我们将使用 SDL 包装器:

chdir(SDL_AndroidGetInternalStoragePath());

接下来,使用 fopen、fwrite、fclose 将存档从缓冲区写入当前工作目录。一旦存档位于 C++ 可以访问的目录中,就将其解压。可以使用两个库的组合来解压 Zip 档案: minizip 和 zlib,第一个可以处理档案的结构,而第二个可以解压数据。
为了获得更多控制和易于移植,我实现了自己的零压缩存档格式,称为 FChest(Flame Steel Chest)。该格式支持将目录与文件一起归档并解压;不支持文件夹层次结构;您只能使用文件。
我们连接 FSCest 库的标头,解压存档:

#include "fschest.h" 
FSCHEST_extractChestToDirectory(archivePath, SDL_AndroidGetInternalStoragePath()); 

解压后,C/C++ 接口将可以访问存档中的文件。因此,我不必重写引擎中文件的所有工作,而只需在启动阶段添加文件解包。

来源

https://developer.android.com/ndk/参考/组/资产

源代码

https://gitlab.com/demensdeum/space-捷豹动作角色扮演游戏
https://gitlab.com/demensdeum/fschest

堆栈机和 RPN

假设我们需要实现一个简单的字节码解释器,我们应该选择什么方法来实现这个任务?

数据结构 堆栈提供了实现简单字节码机的能力。堆栈机的特性和实现在西方和国内互联网上有很多文章介绍,我只提一下Java虚拟机是堆栈机的一个例子。

机器的操作原理很简单,将包含数据和操作码(操作码)的程序提供给输入,并使用堆栈操作来实现必要的操作。让我们看一下我的堆栈机中的字节码程序示例:

пMVkcatS olleHП
 

На выходе мы получим строку “Hello StackVM”.堆栈机从左到右读取程序,当操作码出现在符号“&#8211”中时,将数据逐个字符加载到堆栈中。使用堆栈实现命令。

在nodejs中实现堆栈机的示例:

逆波兰表示法 (RPN)

堆栈机也很容易用来实现计算器,为此它们使用逆波兰表示法(后缀表示法)。
常规中缀表示法的示例:
2*2+3*4

转换为 RPN:
22*34*+

为了计算后缀记录,我们使用堆栈机:
2–到堆栈顶部(堆栈:2)
2–到堆栈顶部(堆栈:2,2)
*–两次获取栈顶,相乘,送入栈顶(栈:4)
3–到堆栈顶部(堆栈:4, 3)
4–到堆栈顶部(堆栈:4,3,4)
*–两次获取栈顶,将结果相乘,送入栈顶(栈:4, 12)
+–两次获取栈顶,将结果相加,发送到栈顶(stack: 16)

如您所见–操作16的结果保留在堆栈上,可以通过实现堆栈打印操作码来打印它,例如:
p22*34*+P

P–堆栈打印开始操作码,p –用于完成堆栈打印并发送最后一行进行渲染的操作码。
为了将算术运算从中缀转换为后缀,使用了 Edsger Dijkstra 的称为“排序码”的算法。上面可以看到一个实现示例,或者在下面的nodejs机器堆栈项目的存储库中可以看到。

来源

https:// /tech.badoo.com/ru/article/579/interpretatory-bajt-kodov-svoimi-rukami/
https://ru.wikipedia.org/wiki/Обратная_польская_запись

源代码

https://gitlab.com/demensdeum/stackvm/< /p>

骨骼动画(第2部分–节点层次结构、插值)

我将继续描述在 Flame Steel Engine 中实现的骨骼动画算法。

由于该算法是我实现的所有算法中最复杂的,因此有关开发过程的注释中可能会出现错误。在上一篇关于该算法的文章中,我犯了一个错误;骨骼数组被单独传输到每个网格的着色器,而不是整个模型。

节点层次结构

为了使算法正常工作,模型必须包含骨骼之间的连接(图)。让我们想象一下同时播放两个动画的情况 –跳起来并举起你的右手。跳跃动画必须沿 Y 轴抬起模型,而手臂抬起动画必须考虑到这一点并在模型跳跃时随模型上升,否则手臂将自行保持在原位。

我们将描述这种情况下的节点连接–身体包含手。在制定算法时,将读取骨骼图,所有动画都将被考虑到正确的连接。在模型的内存中,图形与所有动画分开存储,只是为了反映模型骨骼的连接性。

CPU 上的插值

上一篇文章我讲述了渲染骨骼动画的原理—— “每个渲染帧变换矩阵都会从 CPU 传输到着色器。”

每个渲染帧都在 CPU 上进行处理;对于每个网格骨骼,引擎都会使用位置插值、旋转和缩放接收最终的变换矩阵。在对最终骨骼矩阵进行插值时,会遍历所有活动节点动画的节点树,最终矩阵与父矩阵相乘,然后发送到顶点着色器进行渲染。

向量用于位置插值,四元数用于旋转;因为与欧拉角不同,它们非常容易插值 (SLERP),而且也很容易表示为变换矩阵。

如何简化实施

为了更轻松地调试顶点着色器,我使用 FSGLOGLNEWAGERENDERER_CPU_BASED_VERTEX_MODS_ENABLED 宏在 CPU 上添加了顶点着色器的模拟。显卡制造商 NVIDIA 有一个用于调试着色器代码的实用程序 Nsight,也许它也可以简化复杂的顶点/像素着色器算法的开发,但我始终无法在 CPU 上测试其功能;

在下一篇文章中,我计划描述混合多个动画并填补剩余的空白。

来源

https://www.youtube.com/watch?v= f3Cr8Yx3GGA

添加对 C++ 中 JavaScript 脚本的支持

在这篇文章中,我将描述一种使用 Tiny-JS 库向 C++ 应用程序添加对 JavaScript 脚本的支持的方法。

Tiny-JS 是一个嵌入 C++ 的库,提供 JavaScript 代码的执行,并支持绑定(能够从脚本调用 C++ 代码)

起初我想使用流行的库 ChaiScript、Duktape 或 connect Lua,但由于依赖关系以及移植到不同平台可能存在的困难,所以决定寻找一个简单、最小但功能强大的 MIT Tiny- JS 符合这些标准。这个库的唯一缺点是缺乏作者的支持/开发,但它的代码非常简单,这允许您在必要时接管支持。

从存储库下载 Tiny-JS:
https://github.com/gfwilliams/tiny-js

接下来,将 Tiny-JS 标头添加到负责脚本的代码中:

#include "tiny-js/TinyJS.h"
#include "tiny-js/TinyJS_Functions.h"

将TinyJS .cpp文件添加到构建阶段,然后就可以开始编写加载和运行脚本了。

存储库中提供了使用该库的示例:
https://github.com/gfwilliams/tiny-js/blob/master/Script.cpp
https://github.com/gfwilliams/tiny-js/blob/wiki/CodeExamples.md

处理程序类的实现示例可以在 SpaceJaguar 项目中找到:
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/src/Controllers/SpaceJaguarScriptController/SpaceJaguarScriptController.h
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/src/Controllers/SpaceJaguarScriptController/SpaceJaguarScriptController.cpp

添加到应用程序的游戏脚本示例:
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/resources/com.demensdeum.spacejaguaractionrpg.scripts.sceneController.js

来源

https://github.com/gfwilliams/tiny-js
https://github.com/dbohdan/embedded-scripting-languages
https://github.com/AlexKotik/embeddable-scripting-languages

在 Linux 上构建适用于 iOS 的 C++ SDL 应用程序

在这篇文章中,我将描述在 Linux 上为 iOS 构建 C++ SDL 应用程序的过程,在没有付费 Apple Developer 订阅的情况下签署 ipa 存档,以及在没有越狱的情况下使用 macOS 将其安装在干净的设备 (iPad) 上。< /p>

首先,让我们安装 Linux 的构建工具链:
https://github.com/tpoechtrager/cctools-port

需要从存储库下载工具链,然后按照Godot Engine网站上的说明完成安装:
https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html

目前,您需要下载 Xcode dmg 并从那里复制 sdk 来构建 cctools-port。此阶段在 macOS 上更容易完成;只需从已安装的 Xcode 复制必要的 sdk 文件即可。成功编译后,终端将包含交叉编译器工具链的路径。

接下来您可以开始构建适用于 iOS 的 SDL 应用程序。让我们打开 cmake 并添加必要的更改来构建 C++ 代码:

SET(CMAKE_SYSTEM_NAME Darwin)
SET(CMAKE_C_COMPILER arm-apple-darwin11-clang)
SET(CMAKE_CXX_COMPILER arm-apple-darwin11-clang++)
SET(CMAKE_LINKER arm-apple-darwin11-ld)

现在您可以使用 cmake 和 make 进行编译,但不要忘记将 $PATH 添加到交叉编译器工具链:


PATH=$PATH:~/Sources/cctools-port/usage_examples/ios_toolchain/target/bin

为了与框架和SDL正确链接,我们将它们编写在cmake中,例如游戏Space Jaguar的依赖项:


target_link_libraries(
${FSEGT_PROJECT_NAME}
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libclang_rt.ios.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_mixer.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_image.a
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreServices.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/ImageIO.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Metal.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AVFoundation.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/GameController.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreMotion.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreGraphics.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AudioToolbox.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreAudio.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/QuartzCore.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/OpenGLES.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/UIKit.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Foundation.framework"
)

就我而言,SDL、SDL_Image、SDL_mixer 库预先在 macOS 上的 Xcode 中编译以进行静态链接;从 Xcode 复制的框架。还添加了 libclang_rt.ios.a 库,其中包括特定于 iOS 的运行时调用,例如 isOSVersionAtLeast。包含一个用于 OpenGL ES 的宏,禁用移动版本中不支持的功能,类似于 Android。

解决所有构建问题后,您应该获得 ARM 的已组装二进制文件。接下来,让我们考虑在没有越狱的设备上运行组装的二进制文件。

在 macOS 上,安装 Xcode,在 Apple 门户上注册,无需支付开发者计划费用。在Xcode中添加帐户->首选项->帐户、创建空白应用程序并在真实设备上构建。在组装过程中,设备将被添加到免费的开发者帐户中。组装并启动后,您需要构建存档才能执行此操作,选择 Generic iOS Device and Product ->档案。构建存档后,从中提取embedded.mobileprovision和PkgInfo文件。从设备的构建日志中,找到具有正确签名密钥的 codesign 行、扩展名为 app.xcent 的授权文件的路径,将其复制。

从压缩包中复制.app文件夹,将压缩包中的二进制文件替换为Linux中交叉编译器编译的二进制文件(例如SpaceJaguar.app/SpaceJaguar),然后将必要的资源添加到.app中,检查存档中 .app 中的 PkgInfo 和Embedded.mobileprovision 文件的完整性,如有必要,请再次复制。我们使用协同设计命令 – 重新签名 .app codesign 需要输入密钥进行签名,即权利文件的路径(可以使用 .plist 扩展名重命名)

重新签名后,创建一个 Payload 文件夹,将扩展名为 .app 的文件夹移动到那里,在根目录中创建一个包含 Payload 的 zip 存档,并使用 .ipa 扩展名重命名该存档。之后,在 Xcode 中,打开设备列表并将新的 ipa 拖放到设备的应用程序列表中;通过 Apple Configurator 2 安装不适用于此方法。如果重新签名正确完成,则具有新二进制文件的应用程序将安装在具有 7 天证书的 iOS 设备(例如 iPad)上,这对于测试期来说足够了。

来源

https://github.com/tpoechtrager/cctools-port
https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html
https://jonnyzzz.com/blog/2018/06/13/link-error-3/
https://stackoverflow.com/questions/6896029/re-sign-ipa-iphone
https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html

游戏愿景#4

有关 Games Vision 游戏的非常不一致的专栏的第四版。

恐怖世界(跨平台,panstasz) – Roguelike 抢先体验游戏风格呃什么?带有RPG元素的文字恐怖冒险?图形上让人想起 80 年代的游戏,您可以从具有变化的 1 位或 2 位调色板中进行选择。

起初,操控方式看起来很奇怪,但随着时间的推移,你就会习惯它,因为这就是为什么它是一款 Roguelike 游戏——在各个方面都给人惊喜和原创。令人惊叹的芯片音乐、80 年代末的日本美学、受伊藤润二作品启发的视觉效果、洛夫克拉夫特风格的奇异故事、几乎无穷无尽的可重玩性。
您还需要什么?
评分:9/10

永恒城堡 [REMASTERED](PC、Daniele维西南佐、朱利奥·佩罗内、伦纳德·门基亚里) –另一个世界风格的现代游戏,闪回。调色板已专门缩减为 CGA 颜色。描述这款游戏必须从它创作的传奇开始:1987年左右,永恒城堡开发商的一位孩子看到了这款游戏,并终生铭记在心,结果这款游戏从未发行,但。 2019年找到并恢复了源代码,发布了改进版本。不过,Steam 上的描述包含的信息是,这是 1987 年畅销游戏的重制版,但那年并没有发布同名游戏,真假由你决定。

图形和游戏玩法显然是为那些喜欢怀旧美好时光的人量身定制的;很多时候,游戏似乎已经冻结了,但实际上你只需按下移动或动作按钮即可看到什么。正在屏幕上发生。这种噱头会造成一种尴尬和失控的感觉,这种感觉在旧游戏中经常使用,但在现代游戏中已被完全放弃。
评分:8/10

死亡与死亡税收(PC、Placeholder Gameworks)–你是否曾梦想过成为一名法官和刽子手?你喜欢长长的黑袍、金属辫子和响指关节吗?那么这就是最适合您的游戏,因为“您唯一无法避免的两件事就是死亡和税收。”

这是一款独一无二的死亡天使模拟器,你必须选择谁生谁死。除了杀死或赋予生命之外,您还可以在智能手机上阅读新闻源,看看您的选择如何影响地球世界。你还需要和你的顶头上司Faith(Fate)沟通,购买物品和各种餐桌用具,比如我给自己买了一个残酷的仙人掌。别忘了对着镜子说话,这很令人兴奋。在缺点中,值得注意的是游戏中发生的事情的普遍亲密性;在游戏几天后,游戏变得有点单调。
评分:8/10

修复 Windows 10 中 HDD 速度缓慢的问题

此说明谨献给所有不放弃的硬盘用户。


Original (Mae Mu)

使用配备双 HDD (Windows 10) 和 SSD (Ubuntu) 的 HP Pavilion 笔记本电脑 1.5 年之后,我开始注意到应用程序的加载时间很长,界面普遍无响应,并且在最简单的操作上冻结在 Windows 10 中。问题被最小化到可以再次使用笔记本电脑的程度。接下来,我将描述我为解决该问题所采取的步骤。

诊断

为了开始研究,我们需要消除任何类型的骗局;首先,让我们确定硬盘故障的主要原因。使用硬盘时可能会出现什么问题?问题可能出现在电子设备的物理层面和逻辑、软件数据层面。
电子问题包括:计算机/笔记本电脑电源不工作、笔记本电脑电池问题;硬盘驱动器组件的磨损、驱动器内部组件的电路和芯片问题、固件错误、驱动器冲击/跌落的后果或影响其运行的其他设备的类似问题。
硬盘驱动器的严重磨损被认为是出现大量坏扇区(坏块)以致驱动器无法进一步运行的时刻。这些块被硬盘固件阻挡,数据会自动转移到其他扇区,直到某个关键时刻才应该影响磁盘的运行。
程序逻辑问题包括由于应用程序操作不正确而导致的文件系统错误、用户操作:热时关闭设备、在没有正确停止应用程序的情况下完成录制过程、驱动程序错误、操作系统服务。
如果没有专门的电子诊断工具,我们只能检查软件层面的正确性;在此过程中,可能会发现电子问题,通常通过块修复方法(更换元件/芯片)来排除;接下来,我们将考虑使用诊断实用程序的软件诊断方法。值得注意的是,所有实用程序都必须以最高优先级在系统上启动,因为其他应用程序可能会干扰性能测量并阻止磁盘读/写,这将导致错误的诊断结果。

智能

S.M.A.R.T.存储设备状态监控系统HDD、SDD、eMMC等。允许您评估设备的磨损情况,查看坏块数量,并根据数据采取进一步的措施。您可以在不同的应用程序中查看 SMART 以使用磁盘;我更喜欢使用制造商提供的实用程序。对于我的希捷硬盘,我使用了 SeaTools 实用程序,其状态显示为 GOOD,即磁盘固件认为一切正常。

制造商实用程序

磁盘制造商的实用程序提供测试来验证其操作。 SeaTools 有多种类型的测试,您可以使用它们来定位问题。快速而简单的测试可能不会发现任何问题,因此更喜欢长时间的测试。就我而言,只有长测试发现了错误。

慢行

为了检查读取的正确性,查找慢速或死块,我编写了一个应用程序 Slowride,它的工作原理非常简单——打开块设备描述符,使用指定的用户设置,读取整个设备的数据,使用时间测量,慢速块的输出。程序在出现第一个错误时停止;在这种情况下,您将不得不转向更严肃的实用程序来删除数据,因为无法使用简单的方法读取磁盘数据。
就我而言,读取整个磁盘的操作正确,但速度略有下降 –在磁盘的某些区域上,一秒内达到 90MB/秒 (5400rpm)。由此可以得出结论,我正在处理一个软件问题。

声学分析

此方法不适用于软件诊断方法,但对于修复问题相当重要。例如,如果电源部分工作,硬盘驱动器可能会冻结/冻结并发出很大的咔哒声。
就我而言,在 Windows 10 中使用磁盘时,我听到了所有 HDD 用户都熟悉的声音,
尝试在操作系统中执行某些操作时,磁盘头来回运行的巨大破裂声,但声音几乎是恒定的,这让我认为碎片太多磁盘,后台服务导致磁盘过载。

修复

软件诊断期间未检测到电子问题;整个磁盘的逐块读取正确完成,但 SeaTools 在长时间测试期间显示错误。

制造商实用程序

除了诊断之外,磁盘制造商的软件还提供错误纠正程序。在 SeaTools 中,“全部修复”按钮负责执行此操作;在确认您同意可能丢失数据后,将开始更正过程。此修复对我的情况有帮助吗?不,磁盘继续大声且缓慢地运行,但长时间测试不再显示错误。

CHKDSK

CHKSDK 是一款 Microsoft 实用程序,用于排除 Windows 文件系统的软件错误。随着时间的推移,此类错误会在磁盘上累积,并可能极大地干扰工作,包括导致根本无法读取/写入任何数据。您可以在 Microsoft 网站上找到使用该实用程序的说明,但我建议使用所有可能的标志来纠正错误(在撰写本文时,这是 /r /b /f);您需要通过Windows终端(cmd)以管理员权限运行扫描,对于系统分区,扫描将在系统启动时进行,并且可能需要很长时间,在我的例子中需要12个小时。
此修复对我的情况有帮助吗?没有。

磁盘碎片整理

磁盘上的数据按块进行处理;大文件通常写入多个块/片段。随着时间的推移,许多删除的文件会创建不在附近的空块,因此,在写入文件时,它们会填充这些空白,并且磁盘头必须在物理上移动很长的距离。这个问题称为碎片,只有硬盘用户才会遇到。在几次修复时,我的硬盘碎片为41%,直观上看起来像这样:

也就是说,一切都很糟糕。您可以使用碎片整理实用程序或内置碎片整理程序查看碎片并对其进行碎片整理。您还可以启用“优化驱动器”服务。在 Windows 10 中,在控制面板中安排碎片整理。 只有 HDD 驱动器需要碎片整理;SSD 驱动器不建议启用碎片整理,因为这会导致磁盘磨损加速,显然出于这个原因,后台碎片整理默认处于禁用状态。

另一个已知的碎片整理选项是“将数据传输到另一个磁盘、格式化磁盘并将数据复制回来。在这种情况下,数据将被写入完全空的扇区,同时保持系统操作的正确逻辑结构。此选项在重置正常复制期间可能不会移动的潜在关键元数据时充满了问题。

禁用服务

使用 Mark Russinovich 的实用程序进程监视器您可以跟踪加载硬盘及其工作的进程,只需启用 IO 写入/读取列即可。研究完本专栏后,我通过控制面板服务面板禁用了 Xbox Game Bar 服务,这是众所周知的 Superfetch 程序的后台加速服务,名称为 SysMain。 Superfetch 必须不断分析用户使用的应用程序,并通过缓存到 RAM 来加速其启动;在我的例子中,这导致了整个磁盘的后台加载并且无法工作。

清理磁盘

我还删除了旧的应用程序和不必要的文件,从而释放了正确的碎片扇区,简化了操作系统的操作,减少了无用的、繁重的服务和程序的数量。

总计

什么最有帮助?对磁盘进行碎片整理后,性能出现了显着差异;通过禁用 Xbox 和 Superfetch 服务消除了自发冻结。如果我使用SSD就不会出现这些问题了吗?肯定不会出现由于碎片而导致运行缓慢的问题,无论如何都必须修复服务问题,并且软件错误不取决于驱动器的类型。在不久的将来,我计划完全过渡到 SSD,但现在“煎饼万岁,煎饼永远!”

链接

http://www.outsidethebox.ms/why-windows-8-defragments-your-ssd-and-how-you-can-avoid-this/
https://channel9.msdn.com/Shows/The-Defrag-Show
https://www.seagate.com/ru/ru/support/downloads/seatools/
https://www.ccleaner.com/defraggler/download
https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/chkdsk
https://gitlab.com/demensdeum/slowride/

块设备的 Slowride 基准测试

慢行&#8211;用于检查具有 /dev/sd* root 访问权限的 POSIX 兼容操作系统的块设备读取速度的实用程序。您可以使用时间阈值来测试块设备的读取性能,以诊断读取性能。
在整个设备上读取 100mb 块的命令,输出高于 2 秒阈值的块:

sudo ./slowride /dev/sda 100 2000

源代码

https://gitlab.com/demensdeum/slowride

太空美洲虎 3D 动作角色扮演游戏

我已经很长时间没有宣布新项目了)我要开始的下一个项目是–名为《Space Jaguar》的 3D 动作角色扮演游戏,一个以科幻为背景的故事,讲述了一个名叫 Jag 的硬汉和他寻找失踪父亲的艰难冒险。火焰钢引擎(或可能是任何其他流行的引擎)上将有 3D 图形,使用过去项目(死亡面具、立方体艺术项目)的开发成果、包含许多参考资料的喜剧情节、街机战斗和 Boss。我还没有准备好谈论完整版本的发布日期;我计划分批发布游戏。

项目存储库:
https://gitlab.com/demensdeum/space-jaguar-action-rpg

用C++ FCGI编写后端服务器

关于我如何为 3D 编辑器 Cube Art Project 编写服务器部分的简要说明,服务器应该保存并显示 Web 版本用户的工作,使用保存按钮为他们提供短 URL。起初我想使用 Swift/PHP/Ruby/JS 或一些类似的现代语言作为后端,但在考虑了我的 VPS 的特点后,我决定用 C/C++ 来编写服务器。
首先,您需要在服务器上安装 libfcgi 以及 Web 服务器的 fcgi 支持模块,例如 Ubuntu 和 Apache:

sudo apt install libfcgi libapache2-mod-fcgid

接下来我们在config中配置模块:

FcgidMaxProcessesPerClass –每个类的最大进程数,我将其设置为 1 个进程,因为我不希望有很大的负载。
AddHandler fcgid-script .fcgi – fcgi 模块应启动的文件扩展名。
将启动 cgi 应用程序的文件夹添加到配置中:

接下来,我们用 C/C++ 编写一个支持 fcgi 的应用程序,对其进行汇编,并将其复制到 /var/www/html/cgi-bin 文件夹中。
代码和构建脚本示例:
https://gitlab.com/demensdeum/cube-art-project-server/-/blob/master/src/cubeArtProjectServer.cpp
https://gitlab.com/demensdeum/cube-art-project-server/-/blob/master/src/build.sh
此后您需要重新启动您的网络服务器:

systemctl restart apache2

接下来,输入必要的权限以通过 chmod 执行 cgi-bin 文件夹。
之后,您的 cgi 程序应该使用链接通过浏览器运行,例如 Cube Art Project 服务器:
http://192.243.103.70/cgi-bin/cubeArtProject/cubeArtProjectServer.fcgi
如果出现问题,请查看 Web 服务器日志,或使用调试器连接到正在运行的进程;调试过程不应与调试常规客户端应用程序的过程有所不同。

来源

https://habr.com/ru/post/154187/
http://chriswu.me/blog/writing-hello-world-in-fcgi-with-c-plus-plus/

源代码

https://gitlab.com/demensdeum/cube-art -项目服务器

立方体艺术项目

立方体艺术项目–立方体 3D 编辑器。
您有一个绝佳的机会在舞台上移动,使用 WSAD + E 按钮构建和删除立方体,旋转鼠标滚轮更改立方体的颜色。目前仅支持 16 种颜色,但未来计划进行许多改进。

网页版
https://demensdeum.com/games/CubeArtProjectWEB/

Windows
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProjectWin32.zip

macOS
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProjectMacOS.zip

Linux (x86-64)
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProjectLinux86_64.zip

安卓
(概念,需要USB鼠标)
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProject.apk

源代码
https://gitlab.com/demensdeum/cube-art-project-bootstrap
https://gitlab.com/demensdeum/cube-art-project-server

技术:SDLEmscriptenMinGWGlewGLMCpp-JSON

将 C++ SDL 应用程序移植到 Android

在这篇文章中,我将描述我移植 3D 编辑器原型的经验 Android 上的 Cube Art Project
首先,让我们看看模拟器中运行的结果;带有红色 3D 立方体光标的编辑器:

为了成功组装,您必须执行以下操作:

  1. 安装最新的 Android SDK 和 NDK(NDK 版本越新越好)。
  2. 下载 SDL2 源代码,从那里获取模板来构建 Android 应用程序。
  3. 将 SDL Image、SDL Mixer 添加到程序集中。
  4. 添加我的游戏引擎和工具包的库及其依赖项(GLM、现代 C++ 的 JSON)
  5. 调整 Gradle 的程序集文件。
  6. 调整 C++ 代码以与 Android 兼容,更改受影响的平台相关组件(OpenGL ES、图形上下文初始化)
  7. 在模拟器上构建并测试项目。

项目模板

加载源SDL、SDL Image、SDL Mixer:
https://www.libsdl.org/download-2.0.php
docs 文件夹包含使用 android 项目模板的详细说明;将 android-project 目录复制到单独的文件夹,创建符号链接或将 SDL 文件夹复制到 android-project/app/jni。
我们用正确的标识符替换 avd 标志,从 Sdk:

目录启动 android 模拟器

cd ~/Android/Sdk/emulator
./emulator -avd Pixel_2_API_24

在脚本中指定路径,组装项目:

rm -rf app/build || true
export ANDROID_HOME=/home/demensdeum/Android/Sdk/
export ANDROID_NDK_HOME=/home/demensdeum/Android/android-ndk-r21-beta2/
./gradlew clean build
./gradlew installDebug

应使用文件中的 C 代码组装 SDL 项目模板

android-sdl-test-app/cube-art-project-android/app/jni/src/YourSourceHere.c

依赖项

下载SDL_image、SDL_mixer的源代码:
https://www.libsdl.org/projects/SDL_image/
https://www.libsdl.org/projects/SDL_mixer/

加载项目的依赖项,例如我的共享库:
https://gitlab.com/demensdeum/FlameSteelCore/
https://gitlab.com/demensdeum/FlameSteelCommonTraits
https://gitlab.com/demensdeum/FlameSteelBattleHorn
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkit/
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL
https://gitlab.com/demensdeum/FSGL
https://gitlab.com/demensdeum/cube-art-project

我们将所有这些上传到 app/jni,每个“模块”放入一个单独的文件夹,例如 app/jni/FSGL。接下来,您可以选择查找 Application.mk 和 Android.mk 文件的工作生成器,我没有找到它们,但也许有一个基于 CMake 的简单解决方案。点击链接并开始熟悉 Android NDK 的程序集文件格式:
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk

您还应该了解 NDK 中不同的 APP_STL 实现:
https://developer.android.com/ndk/guides/cpp-support.html

熟悉后,我们为每个“模块”创建一个 Android.mk 文件,然后是共享库 Cube-Art-Project 的示例汇编文件:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

APP_STL := c++_static
APP_CPPFLAGS := -fexceptions
LOCAL_MODULE := CubeArtProject
LOCAL_C_INCLUDES := $(LOCAL_PATH)/src $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/FlameSteelCommonTraits/src/FlameSteelCommonTraits
LOCAL_EXPORT_C_INCLUDES = $(LOCAL_PATH)/src/

define walk
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
endef

ALLFILES = $(call walk, $(LOCAL_PATH)/src)
FILE_LIST := $(filter %.cpp, $(ALLFILES))
$(info CubeArtProject source code files list)
$(info $(FILE_LIST))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

LOCAL_SHARED_LIBRARIES += FlameSteelCore
LOCAL_SHARED_LIBRARIES += FlameSteelBattleHorn
LOCAL_SHARED_LIBRARIES += FlameSteelCommonTraits
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkit
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkitFSGL
LOCAL_SHARED_LIBRARIES += FSGL
LOCAL_SHARED_LIBRARIES += SDL2
LOCAL_SHARED_LIBRARIES += SDL2_image

LOCAL_LDFLAGS := -static-libstdc++
include $(BUILD_SHARED_LIBRARY)

任何有经验的 CMake 用户都会从第一行开始理解这个配置,格式非常相似,Android.mk 缺少 GLOB_RECURSIVE,所以你必须使用 walk 函数递归搜索源文件。

我们更改 Application.mk、Android.mk 来构建 C++ 而不是 C 代码:

APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM=android-16
APP_STL := c++_static
APP_CPPFLAGS := -fexceptions

重命名YourSourceHere.c -> YourSourceHere.cpp,grep条目,更改程序集中的路径,例如:

app/jni/src/Android.mk:LOCAL_SRC_FILES := YourSourceHere.cpp

接下来,尝试构建项目,如果编译器看到缺少标头的错误,请检查 Android.mk 中路径的正确性;如果链接器出现“未定义引用”等错误,请检查程序集中的源代码文件是否指定正确;可以通过在 Android.mk 文件中指定 $(info $(FILE_LIST)) 来跟踪列表。不要忘记双重链接机制,使用 LOCAL_SHARED_LIBRARIES 键中的模块并通过 LD 进行正确链接,例如 FSGL:

LOCAL_LDLIBS := -lEGL -lGLESv2

适应和启动

我必须改变一些东西,例如,从 iOS 和 Android 的构建中删除 GLEW,重命名一些 OpenGL 调用,添加 EOS 后缀(glGenVertexArrays -> glGenVertexArraysOES),包含用于缺少的现代调试功能的宏,锦上添花的是隐式包含指示宏的 GLES2 标头GL_GLEXT_PROTOTYPES 1:

#define GL_GLEXT_PROTOTYPES 1
#include "SDL_opengles2.h"

我还观察到第一次启动时出现黑屏,并出现类似“E/libEGL: validate_display:255 error 3008 (EGL_BAD_DISPLAY)”的错误,更改了 SDL 窗口的初始化、OpenGL 配置文件,一切正常:

SDL_DisplayMode mode;
SDL_GetDisplayMode(0,0,&mode);
int width = mode.w;
int height = mode.h;

window = SDL_CreateWindow(
            title,
            0,
            0,
            width,
            height,
            SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE
        );

SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES );

在模拟器上,应用程序默认安装为 SDL 图标和名称“Game”。

我只需探索基于CMake自动生成程序集文件的可能性,或者将所有平台的程序集迁移到Gradle即可;然而,CMake 仍然是正在进行的 C++ 开发的事实上的选择。

源代码

https://gitlab.com/demensdeum/android- sdl-test-app
https://gitlab.com/demensdeum/android-sdl-test-app/tree/master/cube-art-project-android

来源

https://developer.android.com/ ndk/guides/cpp-support.html
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk
https://lazyfoo.net/tutorials/SDL/52_hello_mobile/android_windows/index.php
https://medium.com/androiddevelopers/getting-started-with-c-and-android-native-activities-2213b402ffff

颠倒的世界

为了开发一个新项目,Cube Art Project 采用了测试驱动开发方法。在这种方法中,首先实现对应用程序的特定功能的测试,然后再实现该特定功能。我认为这种方法的一大优点是最终接口的实现,在功能开发开始之前尽可能不涉及实现细节。通过这种方法,当接口是特定实现的契约时,测试指示进一步的实现,添加契约编程的所有好处。
立方体艺术项目–一种 3D 编辑器,用户可以在其中从立方体构建图形;不久前,这种类型非常流行。由于这是一个图形应用程序,我决定添加带有屏幕截图验证的测试。
要验证屏幕截图,您需要从 OpenGL 上下文中获取它们,这是使用 glReadPixels 函数完成的。函数参数的描述很简单–起始位置、宽度、高度、格式(RGB/RGBA/等)、指向输出缓冲区的指针;任何使用过 SDL 或具有 C 数据缓冲区经验的人都可以简单地替换必要的参数。然而,我认为有必要描述一下 glReadPixels 输出缓冲区的一个有趣的功能;像素从下到上存储在其中,而在 SDL_Surface 中,所有基本操作都是从上到下发生的。
也就是说,从 png 文件加载参考屏幕截图后,我无法直接比较两个缓冲区,因为其中一个缓冲区是颠倒的。
要从 OpenGL 翻转输出缓冲区,您需要通过减去 Y 坐标的屏幕截图高度来填充它。但是,值得考虑的是,如果在填充时不减去它,则有可能超出缓冲区限制。导致内存损坏。
由于我总是尝试使用“通过接口编程”的 OOP 范例,而不是通过指针直接进行类似 C 的内存访问,因此当我尝试在缓冲区外部写入数据时,由于方法中的边界验证,对象会通知我这一点.
自上而下的截图方法最终代码:

    auto width = params->width;
    auto height = params->height;

    auto colorComponentsCount = 3;
    GLubyte *bytes = (GLubyte *)malloc(colorComponentsCount * width * height);
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, bytes);

    auto screenshot = make_shared(width, height);

    for (auto y = 0; y < height; y++) {
        for (auto x = 0; x < width; x++) {
            auto byteX = x * colorComponentsCount;
            auto byteIndex = byteX + (y * (width * colorComponentsCount));
            auto redColorByte = bytes[byteIndex];
            auto greenColorByte = bytes[byteIndex + 1];
            auto blueColorByte = bytes[byteIndex + 2];
            auto color = make_shared(redColorByte, greenColorByte, blueColorByte, 255);
            screenshot->setColorAtXY(color, x, height - y - 1);
        }
    }

    free(bytes);

来源

https://community.khronos.org/ t/glreadpixels-fliped-image/26561
https://stackoverflow.com/questions/8346115/why-are-bmps-stored-upside-down

源代码

https://gitlab.com/demensdeum/cube-艺术项目引导

最长公共子串

在这篇文章中,我将描述一种解决最大公共子串问题的算法。假设我们正在尝试解密加密的二进制数据,首先让我们尝试通过搜索最大的子字符串来找到常见模式。
输入字符串示例:
adasDATAHEADER??jpjjwerthhkjbcvkDATAHEADER??kkasdf
我们正在寻找重复两次的字符串:
数据头??

前缀

首先,我们编写一个方法来比较两个字符串的前缀,让它返回结果字符串,其中左前缀的字符与右前缀的字符相等。
例如,对于以下行:

        val lhs = "asdfWUKI"
        val rhs = "asdfIKUW"

结果字符串 –阿斯达夫
Kotlin 中的示例:

fun longestPrefix(lhs: String, rhs: String): String {
        val maximalLength = min(lhs.length-1, rhs.length -1)
        for (i in 0..maximalLength) {
            val xChar = lhs.take(i)
            val yChar = rhs.take(i)
                if (xChar != yChar) {
                    return lhs.substring(0, i-1)
                }
        }
        return lhs.substring(0,maximalLength)
}

暴力破解

当事情进展不顺利时,你应该诉诸暴力。使用longestPrefix方法,我们将在两个循环中遍历字符串,第一个循环从i到末尾,第二个从i + 1到末尾,将它们传递以搜索最大的前缀。该算法的时间复杂度约为O(n^2) ~ O(n*^3)。
Kotlin 中的示例:

fun searchLongestRepeatedSubstring(searchString: String): String {
        var longestRepeatedSubstring = ""
        for (x in 0..searchString.length-1) {
            val lhs = searchString.substring(x)
            for (y in x+1..searchString.length-1) {
                val rhs = searchString.substring(y)
                val longestPrefix = longestPrefix(lhs, rhs)
                if (longestRepeatedSubstring.length < longestPrefix.length) {
                    longestRepeatedSubstring = longestPrefix
                }
            }
        }
        return longestRepeatedSubstring
}

后缀数组

为了更优雅的解决方案,我们需要一个工具——一种名为“后缀数组”的数据结构。该数据结构是一个循环填充的子字符串数组,其中每个子字符串从该行的下一个字符开始到末尾。
例如,对于该行:

adasDATAHEADER??

后缀数组如下所示:

adasDATAHEADER??
dasDATAHEADER??
asDATAHEADER??
sDATAHEADER??
DATAHEADER??
ATAHEADER??
TAHEADER??
AHEADER??
HEADER??
EADER??
ADER??
DER??
ER??
R??
??
?

我们通过排序来解决

让我们对后缀数组进行排序,然后循环遍历当前元素在左手(左)、下一个在右手(右)的所有元素,并使用longestPrefix计算最长前缀方法。
Kotlin 中的示例:

fun searchLongestRepeatedSubstring(searchString: String): String {
    val suffixTree = suffixArray(searchString)
    val sortedSuffixTree = suffixTree.sorted()

    var longestRepeatedSubstring = ""
    for (i in 0..sortedSuffixTree.count() - 2) {
        val lhs = sortedSuffixTree[i]
        val rhs = sortedSuffixTree[i+1]
        val longestPrefix = longestPrefix(lhs, rhs)
        if (longestRepeatedSubstring.length < longestPrefix.length) {
            longestRepeatedSubstring = longestPrefix
        }
    }
    return longestRepeatedSubstring
}

该算法的时间复杂度为 O(N log N),这比直接解决方案要好得多。

来源

https://en.wikipedia.org/wiki/Longest_common_substring_problem

源代码

https://gitlab.com/demensdeum/algorithms

插入排序、归并排序

插入排序

插入排序–每个元素都会与列表中的前一个元素进行比较,并将元素与较大的元素(如果有)交换,否则内部比较循环停止。由于元素是从第一个到最后一个排序的,因此每个元素都会与已经排序的列表进行比较,这“可能”减少了总体运行时间。该算法的时间复杂度为O(n^2),即与气泡多样性相同。

合并排序

合并排序–该列表被分成一个元素的组,然后这些组被成对地“合并”并同时进行比较。在我的实现中,当合并对时,将左侧的元素与右侧的元素进行比较,然后移动到结果列表;如果左侧的元素消失,则将右侧的所有元素添加到结果列表中; list(不需要进行额外的比较,因为组中的所有元素都会经过排序迭代)< br />该算法的工作非常容易并行化;合并对的阶段可以在线程中执行,等待调度程序中的迭代结束。
单线程执行算法的输出:

["John", "Alice", "Mike", "#1", "Артем", "20", "60", "60", "DoubleTrouble"]
[["John"], ["Alice"], ["Mike"], ["#1"], ["Артем"], ["20"], ["60"], ["60"], ["DoubleTrouble"]]
[["Alice", "John"], ["#1", "Mike"], ["20", "Артем"], ["60", "60"], ["DoubleTrouble"]]
[["#1", "Alice", "John", "Mike"], ["20", "60", "60", "Артем"], ["DoubleTrouble"]]
[["#1", "20", "60", "60", "Alice", "John", "Mike", "Артем"], ["DoubleTrouble"]]
["#1", "20", "60", "60", "Alice", "DoubleTrouble", "John", "Mike", "Артем"]

多线程执行算法的输出:

["John", "Alice", "Mike", "#1", "Артем", "20", "60", "60", "DoubleTrouble"]
[["John"], ["Alice"], ["Mike"], ["#1"], ["Артем"], ["20"], ["60"], ["60"], ["DoubleTrouble"]]
[["20", "Артем"], ["Alice", "John"], ["60", "60"], ["#1", "Mike"], ["DoubleTrouble"]]
[["#1", "60", "60", "Mike"], ["20", "Alice", "John", "Артем"], ["DoubleTrouble"]]
[["DoubleTrouble"], ["#1", "20", "60", "60", "Alice", "John", "Mike", "Артем"]]
["#1", "20", "60", "60", "Alice", "DoubleTrouble", "John", "Mike", "Артем"]

算法时间复杂度为O(n*log(n)),略优于O(n^2)

来源

https://en.wikipedia.org/wiki/Insertion_sort
https://en.wikipedia.org/wiki/Merge_sort

源代码

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/insertionSort
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/mergeSort

Erlang 中的冒泡排序

冒泡排序非常无聊,但是如果您尝试用电信的函数式语言来实现它,它就会变得更有趣——埃尔兰。

我们有一个数字列表,我们需要对其进行排序。冒泡排序算法遍历整个列表,逐对迭代和比较数字。在检查过程中,会发生以下情况:将较小的数字添加到输出列表中,或者在当前列表中交换数字;如果右侧的数字较小,则继续搜索迭代中的下一个数字。重复此遍历,直到列表中不再有替换为止。

实际上,由于该算法的时间复杂度很高,因此不值得使用。 O(n^2);我在 Erlang 中以命令式的方式实现了它,但如果你有兴趣,你可以寻找更好的选择:

-module(bubbleSort).
-export([main/1]).

startBubbleSort([CurrentHead|Tail]) ->
    compareHeads(CurrentHead, Tail, [], [CurrentHead|Tail]).

compareHeads(CurrentHead, [NextHead|Tail], [], OriginalList) ->   
    if
        CurrentHead < NextHead ->
            compareHeads(NextHead, Tail, [CurrentHead], OriginalList);
        true ->
            compareHeads(CurrentHead, Tail, [NextHead], OriginalList)
    end;
    
compareHeads(CurrentHead, [NextHead|Tail], OriginalOutputList, OriginalList) ->
    if
        CurrentHead < NextHead ->
            OutputList = OriginalOutputList ++ [CurrentHead],
            compareHeads(NextHead, Tail, OutputList, OriginalList);
        true ->
            OutputList = OriginalOutputList ++ [NextHead],
            compareHeads(CurrentHead, Tail, OutputList, OriginalList)
    end;
  
compareHeads(CurrentHead, [], OriginalOutputList, OriginalList) ->
    OutputList = OriginalOutputList ++ [CurrentHead],
    if
        OriginalList == OutputList ->
            io:format("OutputList: ~w~n", [OutputList]);
        true ->
            startBubbleSort(OutputList)
    end.
  
main(_) ->
    UnsortedList = [69,7,4,44,2,9,10,6,26,1],
    startBubbleSort(UnsortedList).

安装和启动

在 Ubuntu 中,Erlang 的安装非常简单;只需在终端中输入 sudo apt install erlang 即可。在这种语言中,每个文件必须是一个模块,具有可以外部使用的函数列表——出口。该语言的有趣特征包括没有变量,只有常量,没有 OOP 标准语法(这并不妨碍 OOP 技术的使用),当然还有基于参与者模型的无锁并行计算。

您可以通过交互式 erl 控制台运行该模块,一个接一个地运行命令,或者更简单地通过 escript bubbleSort.erl 运行该模块;对于不同的情况,文件看起来会有所不同,例如,对于 escript,您需要创建一个主函数来启动它。

来源

https://www.erlang.org/
https://habr.com/ru/post/197364/

源代码

https://gitlab.com/ demensdeum/algorithms/blob/master/bubbleSort/bubbleSort.erl

字典序比较算法

字典字符串比较算法的工作原理非常简单;循环比较字符代码,如果字符不相等则返回结果。

C 语言的示例可以在这里找到:
https://github.com/gcc-mirror/gcc/blob/master/libiberty/memcmp.c

应该考虑到您需要比较单个静态编码中的字符,例如在 Swift 中我在 UTF-32 中使用了逐字符比较。使用 memcmp 的数组排序选项将完全适用于单字节字符,在其他情况下(可变长度编码),顺序可能不正确。我不排除基于变长编码实现的可能性,但很可能会复杂一个数量级。

该算法的时间复杂度在最好情况下为 O(1),在平均和最坏情况下为 O(n)

来源

https://ru.wikipedia.org/wiki/Lexicography_order

来源

https://gitlab.com/demensdeum /algorithms/blob/master/lexiCompare/lexiCompare.swift

使用 C 语言进行 ZX Spectrum 游戏开发

这篇废话帖子专门为老ZX Spectrum电脑用C语言开发一个游戏,来看看帅哥吧:

1982年开始生产,一直生产到1992年。该机的技术特点:8位Z80处理器,16-128kb内存和其他扩展,例如AY-3-8910声音芯片。

作为Yandex Retro Games Battle 2019竞赛的一部分,我为这台机器编写了一个游戏叫做Interceptor 2020。由于我没有时间学习Z80的汇编语言,所以我决定用C来开发它。作为工具链,我选择了一套现成的工具链—— z88dk,其中包含 C 编译器和许多辅助库,可加快 Spectrum 应用程序的执行速度。它还支持许多其他 Z80 机器,例如 MSX、德州仪器计算器。

接下来,我将描述我对计算机体系结构、z88dk 工具链的浅薄了解,并展示我如何设法实现 OOP 方法和使用设计模式。

安装功能

z88dk 的安装应该根据存储库中的手册进行,但是,对于 Ubuntu 用户,我想注意一个功能:如果您已经从 deb 包安装了 Z80 编译器,那么您应该删除它们,因为 z88dk 默认情况下将从 bin 文件夹访问它们;由于工具链编译器版本不兼容,您很可能无法编译任何内容。 /p>

你好世界

编写 Hello World 非常简单:

#include 

void main()
{
    printf("Hello World");
}

编译 Tap 文件更加容易:

zcc +zx -lndos -create-app -o helloworld helloworld.c

要运行,请使用任何支持 Tap 文件的 ZX Spectrum 模拟器,例如在线模拟器:
http://jsspeccy.zxdemo.org/

全屏在图片上绘图

tl;dr 图片以图块形式绘制,图块大小为 8×8 像素,图块本身内置于 Spectrum 字体中,然后将图片打印为索引中的一条线。

精灵和图块输出库 sp1 使用 UDG 输出图块。图片被转换为一组单独的 UDG(图块),然后使用索引在屏幕上组装。应该记住,UDG 用于显示文本,如果您的图片包含非常大的图块集(例如,超过 128 个图块),那么您将不得不超出该集的边界并删除默认的 Spectrum字体。为了解决这个限制,我使用了 128 – 的基数。 255,通过简化图像同时保留原始字体。关于简化下面的图片。

要绘制全屏图像,您需要使用三个实用程序来武装自己:
瘸子
img2spec
png2c-z88dk

真正的 ZX 男人、真正的复古战士有一种方法,那就是使用 Spectrum 调色板打开图形编辑器,了解图像输出的特征,手动准备并使用 png2c-z88dk 或 png2scr 上传。< /p>

更简单的方法–拍摄一张32位图像,在Gimp中将颜色数量切换为3-4,稍微编辑一下,然后将其导入到img2spec中,以免手动处理颜色限制,导出png并使用png2c-将其转换为C数组z88dk。

您应该记住,为了成功导出,每个图块不能包含超过两种颜色。

因此,您将收到一个包含唯一图块数量的 h 文件,如果超过 ~128 个,则在 Gimp 中简化图像(增加重复性)并在新图块上执行导出过程.

导出后,您可以从字面上加载图块中的“字体”,并将图块索引中的“文本”打印到屏幕上。下面是渲染“类”的示例:

// грузим шрифт в память
    unsigned char *pt = fullscreenImage->tiles;

    for (i = 0; i < fullscreenImage->tilesLength; i++, pt += 8) {
            sp1_TileEntry(fullscreenImage->tilesBase + i, pt);
    }

    // ставим курсор в 0,0
    sp1_SetPrintPos(&ps0, 0, 0);

    // печатаем строку
    sp1_PrintString(&ps0, fullscreenImage->ptiles);

在屏幕上绘制精灵

接下来我将描述一种在屏幕上绘制 16×16 像素精灵的方法。我没有开始动画和改变颜色,因为……正如我所假设的,在这个阶段我已经耗尽了内存,这是微不足道的。因此,游戏仅包含透明的单色精灵。

我们在Gimp中绘制一个单色png图像16×16,然后使用png2sp1sprite将其转换为asm汇编文件,在C代码中我们从汇编文件中声明数组,并在汇编阶段添加该文件。< /p>

在声明精灵资源阶段之后,必须将其添加到屏幕上所需的位置,下面是游戏对象“类”的代码示例:

    struct sp1_ss *bubble_sprite = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, 0);
    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2,    SP1_TYPE_2BYTE, col2-col1, 0);
    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2RB,  SP1_TYPE_2BYTE, 0, 0);
    sp1_IterateSprChar(bubble_sprite, initialiseColour);

从函数名称就可以大致明白–的含义。为精灵分配内存,添加两个 8×8 列,为精灵添加颜色。

每帧中都会指示精灵的位置:

sp1_MoveSprPix(gameObject->gameObjectSprite, Renderer_fullScreenRect, gameObject->sprite_col, gameObject->x, gameObject->y);

模拟 OOP

C 中没有 OOP 语法,如果你真的想要的话该怎么办?您需要连接您的思想并受到以下想法的启发:不存在 OOP 设备之类的东西;一切最终都属于一种机器架构,其中根本不存在对象的概念以及与之相关的其他抽象。 /p>

这个事实让我在很长一段时间里都无法理解为什么需要 OOP,如果最终一切都变成了机器代码,为什么有必要使用它。

但是,在从事产品开发之后,我发现了这种编程范式的好处,当然主要是开发灵活性、代码保护机制、采用正确的方法、减少熵、简化团队工作。所有上述好处都源于三大支柱——多态、封装、继承。

还值得注意的是解决与应用程序架构相关的问题的简化,因为 80% 的架构问题是在上个世纪由计算机科学家解决的,并在设计模式的文献中进行了描述。接下来,我将描述向 C 添加类似 OOP 的语法的方法。

以C结构体作为存储类实例数据的基础更为方便。当然,您可以使用字节缓冲区,为类、方法创建自己的结构,但为什么要重新发明轮子呢?毕竟,我们已经在重新发明语法。

类数据

游戏对象“类”数据字段示例:

struct GameObjectStruct {
    struct sp1_ss *gameObjectSprite;
    unsigned char *sprite_col;
    unsigned char x;
    unsigned char y;
    unsigned char referenceCount;
    unsigned char beforeHideX;
    unsigned char beforeHideY;
};
typedef struct GameObjectStruct GameObject;

将我们的类保存为“GameObject.h”,在正确的位置执行#include“GameObject.h”并使用它。

类方法

考虑到 Objective-C 语言开发人员的经验,类方法的签名将是全局范围内的函数,第一个参数始终是数据结构,后面是方法参数。下面是“类”GameObject 的“方法”示例:

void GameObject_hide(GameObject *gameObject) {
    gameObject->beforeHideX = gameObject->x;
    gameObject->beforeHideY = gameObject->y;
    gameObject->y = 200;
}

方法调用如下所示:

GameObject_hide(gameObject);

构造函数和析构函数的实现方式相同。可以将构造函数实现为分配器和字段初始值设定项,但我更喜欢单独控制对象分配和初始化。

使用内存

使用 new 和 delete 宏中包含的 malloc 和 free 来手动管理表单内存以匹配 C++:

#define new(X) (X*)malloc(sizeof(X))
#define delete(X) free(X)

对于同时被多个类使用的对象,半手动内存管理是基于引用计数实现的,类似于旧的 Objective-C Runtime ARC 机制:

void GameObject_retain(GameObject *gameObject) {
    gameObject->referenceCount++;
}

void GameObject_release(GameObject *gameObject) {
    gameObject->referenceCount--;

    if (gameObject->referenceCount < 1) { sp1_MoveSprAbs(gameObject->gameObjectSprite, &Renderer_fullScreenRect, NULL, 0, 34, 0, 0);
        sp1_DeleteSpr(gameObject->gameObjectSprite);
        delete(gameObject);
    }
}

因此,每个类必须使用retain声明共享对象的使用,通过release释放所有权。现代版本的 ARC 使用自动保留/释放调用。

声音!

Spectrum 有一个能够再现 1 位音乐的高音扬声器;当时的作曲家能够同时再现多达 4 个声道。

Spectrum 128k包含一个独立的声音芯片AY-3-8910,可以播放跟踪器音乐。

提供了一个用于在 z88dk 中使用高音扬声器的库

还有什么需要学习

我有兴趣熟悉 Spectrum、使用 z88dk 实现游戏并学习很多有趣的东西。我还有很多东西需要学习,例如 Z80 汇编器,因为它使我能够充分利用 Spectrum 的功能、使用内存库以及使用 AY-3-8910 声音芯片。希望明年还能参加比赛!

链接

https://rgb.yandex
https://vk.com/sinc_lair
https://www.z88dk.org/forum/

源代码

https://gitlab.com/demensdeum/ zx-projects/tree/master/interceptor2020

二分查找

假设我们需要查明电子邮件地址“demensdeum@gmail.com”是否包含在允许接收信件的电子邮件地址列表中.

让我们从第一个元素到最后一个元素遍历整个列表,检查该元素是否等于指定的地址–让我们实现一个线性搜索算法。但这需要很长时间,不是吗?

要回答这个问题,请使用“算法的时间复杂度”,“O”符号。最坏情况下线性搜索的操作时间等于数组元素的第n个数量,我们用“O”符号来写它–在)。接下来,我们需要解释一下,对于任何已知的算法,都存在三个性能指标——最好情况、最坏情况和平均情况的执行时间。例如,邮件地址“demensdeum@gmail.com”位于数组的第一个索引中,那么就会在第一步中找到它根据该算法,执行时间至多是“最佳”。 O(1);如果在列表的末尾,那么这是最坏的情况– O(n)

但是软件实现的细节,硬件性能,它们应该影响大O吗?现在吸一口气,想象一下时间复杂度的计算是针对某种抽象的理想机计算的,其中只有这个算法,没有别的。

算法

好吧,事实证明线性搜索相当慢,让我们尝试使用二分搜索。首先,应该澄清的是,我们不会使用二进制数据;由于其工作的特殊性,因此给该方法起了这个名字。最初我们将数组排序为 字典顺序,然后算法取整个数组的范围,获取范围的中间元素,比较按字典顺序,并根据比较的结果,决定使用哪个范围来进一步搜索–当前的上半部分或下半部分。也就是说,在每个搜索步骤中,都会从两个可能的“结果”中做出决定。二元逻辑。重复此步骤,直到找到或未找到该单词(出现范围的下索引和上索引的交集)。

该算法的性能–最好的情况是立即在数组中间找到一个元素 O(1),枚举的最坏情况是 O(log n)

陷阱

在实现二分查找时,我不仅遇到了编程语言库中字典比较缺乏标准化这个有趣的问题,甚至还发现缺乏统一的实现标准JavaScript 内的 localeCompare。 ECMAScript 标准允许该函数有不同的实现,这就是为什么当使用 localeCompare 进行排序时,可以在不同的 JavaScript 引擎上观察到完全不同的结果。

因此,为了使算法正确工作,有必要排序并仅使用相同的字典序比较算法,否则什么都不起作用。但是,例如,如果您尝试在 Scala 中对数组进行排序,并使用 Nodejs 进行搜索,而不实现您自己的排序/排序,那么除了对人性的失望之外,什么也没有等待您。

来源

<一href="https://ru.stackoverflow.com/questions/489888/%D0%A7%D1%82%D0%BE-%D1%82%D0%B0%D0%BA%D0%BE%D0%B5 -% D0%BB%D0%B5%D0%BA%D1%81%D0%B8%D0%BA%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1% 87%D0%B5%D1%81%D0%BA%D0%BE%D0 %B5-%D1%81%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8-%D1%87% D1%82%D0%BE-%D0%BE%D0%BD%D0%BE- %D1%81%D0%BE%D0%B1%D0%BE%D0%B9-%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0% D0%B2%D0%BB%D1%8F%D0%B5%D1%82" target="_blank" rel="noopener">什么是词典比较,它代表什么?
Почему для вычисления сложности алгоритмов используется log N вместо lb N?
Двоичный поиск
Знай сложности алгоритмов
https://stackoverflow.com/questions/52941016/sorting-in-localecompare-in-javascript

源代码

https://gitlab.com/demensdeum/algorithms

图案立面


Façade 指的是结构设计模式。它提供了一个单一的接口,可以处理复杂的系统,允许客户端不需要这些系统的实现细节,从而简化他们的代码,并实现客户端和较低层系统之间的松耦合。 GoF 有一个很好的 Façade 示例:一种编程语言编译器,为追求不同目标的不同客户端提供通过单一编译器外观接口汇编代码的能力。

来源

https://refactoring.guru/ru/design-patterns/facade
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

抽象工厂模式

抽象工厂–提供了一个用于创建相关对象的接口,无需指定特定的类。

我真的很喜欢这个模式的替代名称– 套件(套件)

它与工厂方法非常相似,但是抽象工厂必须描述正在创建的对象之间的关系,否则它简直就是一个上帝对象 创造一切的反模式是随意的。

想象一下为眼镜开发一个 AR 框架;我们在屏幕上显示室内导航箭头、商店图标、有趣的地方、窗口和按钮,以及有关用户当前所在位置的信息。

同时,我们需要能够自定义 AR 环境控件的外观和行为。正是在这种情况下,您需要使用 Set 模式。

我们来写Abstract FactoryAbstract Products的接口–父协议、AR 环境元素:

protocol ARFactory {
    func arrow() -> ARArrow
    func icon() -> ARIcon
    func button() -> ARButton
    func window() -> ARWindow
}

protocol ARArrow {
    var image: { get }
    func handleSelection()
}

protocol ARIcon {
    var image: { get }
    var title: String
}

protocol ARButton {
    var title: String
    func handleSelection()
}

protocol ARWindow {
    var title: String
    var draw(canvas: Canvas)
}

现在套件开发人员需要基于抽象工厂接口实现具体工厂,并且他们必须一起实现所有元素;应用程序的其余部分将能够在不更改代码的情况下使用工厂。< /p>

来源

https://refactoring.guru/ru/design-patterns /抽象工厂
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

工厂方法

工厂方法模式指的是生成式设计模式。
该模式描述了用于创建特定类的对象的接口的创建。看起来很简单,对吧?

理论上

假设我们正在开发一个使用 AR 眼镜的框架,当将头倾斜到一侧时,可用应用程序的菜单应该出现在用户的眼前。应用程序将由第三方公司(我们框架的客户)开发。自然,我们不知道应该出现哪些应用程序、图标、名称,因此我们必须提供一个接口来实现应用程序的图标和相关信息。我们称之为产品:

protocol Product {
 var name: String { get }
 var image: Image { get }
 var executablePath: String { get }
}

接下来,我们需要提供一个接口,以便我们的客户可以为其特定产品实施一系列应用程序的发布。带有名称的应用程序图标数组,我们已经在框架中绘制了它们。

让我们编写这个接口– Creator 接口包含返回产品数组的工厂方法

protocol Creator {
 func factoryMethod() -> [Product]
}

实践

我们 AR 框架的第一个客户是 7B 公司。洪都拉斯领先的咖啡机软件供应商。他们希望销售增强现实眼镜,能够冲泡咖啡、检查水/咖啡豆是否已满,并使用室内地图模式显示前往最近咖啡机的路线。

他们负责软件的开发;我们只需提供有关CreatorProduct界面的文档,以便正确显示应用程序列表及其进一步内容发射。

传输文档后,7B公司利用Creator接口,实现了Specific Creator……返回应用程序图标数组的类。图标应用程序本身是实现Product接口的特定产品类。

特定产品的示例代码:

class CoffeeMachineLocator: implements Product {
 let name = “7B Coffee Machine Locator v.3000”
 let image = Image.atPath(“images/locator.tga”)
 let executablePath = “CoffeeMachineLocator.wasm”
}

class iPuchinno: implements Product {
 let name = “iPuchinno 1.0.3”
 let image = Image.atPath(“images/puchino.pvrtc”)
 let executablePath = “neutron/ipuchBugFixFinalNoFreezeFixAlpha4.js”
}

Concrete Creator,给出两个应用程序的数组:

class 7BAppsCreator: implements Creator {
 func factoryMethod() -> [Product] {
  return [CoffeeMachineLocator(), iPuchinno()]
 }
}

此后,7B公司编译了Concrete ProductsConcrete Creator库,并将其与我们的框架相结合,开始为其咖啡机销售AR眼镜,我们不需要添加

来源

https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

模式命令

命令模式是指行为设计模式。

这是我坚持了最长的时间的模式,它是如此简单,却又非常复杂。但就我个人而言,我发现自学的美妙之处在于你有足够的时间从各个角度研究某个主题。

因此,GoF 中的适用性描述得非常简洁明了:
将请求封装为对象,允许您使用不同的请求参数化客户端、使用队列、记录请求以及执行取消操作。

现在让我们根据描述实现该命令的简单版本:

string fakeTrumpsRequest = “SELECT * from Users where name beginsWith DonaldTrump”

我们将请求封装在一个字符串类对象中,它可以用于配置客户端、向队列添加命令、记录、取消(使用“快照”模式)

在我看来,这足以执行 SQL 查询等操作,但是实现细节、不同的应用程序选项、模式的代码库、客户端角色和辅助类也有很大不同。

材料部分

命令模式以命令协议开始,其中包含单个execute()方法。接下来是具体的命令和接收器,CC实现了对接收器的操作,描述了接收器和动作之间的联系。有什么不清楚的吗?我也是,但我们继续吧。 客户端创建特定命令的实例,并将其与接收器关联。 祈求者 –执行启动命令过程的对象。

现在让我们尝试用一个例子来说明这一点,假设我们要更新 myPhone 上的 myOS,为此我们启动 myOS_Update! 应用程序,在其中按下“立即更新”按钮,10 秒后系统将执行此操作。报告更新成功。

上面示例中的客户端是 myOS_Update! 应用程序,Invoker 是“立即更新!”按钮,它启动特定命令使用execute()方法更新系统,该方法访问接收器–操作系统更新守护进程。

使用示例

让我们接受 myOS_Update 应用程序的 UI!太好了,他们决定将其作为单独的产品出售,以提供更新其他操作系统的界面。在这种情况下,我们将实现一个支持通过库扩展的应用程序,在库中将有特定命令、接收器的实现,我们将保留静态/不可变Invoker客户端,协议命令

因此,不需要支持可变代码,因为我们的代码将保持不变,只有在客户端实现时才会出现问题,因为它们的特定命令接收器。而且,在这个实现中,不需要传输主应用的源代码,即我们使用 Command 模式封装了命令和 UI 交互。

来源

https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

为 Ubuntu OSXCross CMake 构建 macOS 应用程序

在这篇文章中,我将描述如何使用 CMake 和 osxcross 在 Ubuntu 构建机器上为 macOS 构建跨平台 C++ 应用程序。
首先,安装osxcross工具链:
https://github.com/tpoechtrager/osxcross
安装分 3 个阶段进行,下载依赖项:

cd tools
./get_dependencies.sh

从Apple官方网站下载XCode.xip,然后从XCode下载SDK:

./gen_sdk_package_pbzx.sh /media/demensdeum/2CE62A79E62A4404/LinuxSupportStorage/xcode111.xip

我希望您阅读最后一步中的 XCode 许可协议?接下来,使用所需的前缀构建工具链:

INSTALLPREFIX=/home/demensdeum/Apps/osxcross ./build.sh 

现在您可以使用上一步的前缀目录中的 osxcross。让我们为 CMake 添加一个新的构建宏,编写所有必要的内容:

if (OSXCROSS)
SET(CMAKE_SYSTEM_NAME Darwin)
SET(CMAKE_C_COMPILER o64-clang)
SET(CMAKE_CXX_COMPILER o64-clang++)
SET(CMAKE_C_COMPILER_AR x86_64-apple-darwin19-ar)
SET(CMAKE_CXX_COMPILER_AR x86_64-apple-darwin19-ar)
SET(CMAKE_LINKER x86_64-apple-darwin19-ld)
SET(ENV{OSXCROSS_MP_INC} 1)
endif()

动态链接对我来说并不成功,因此我们静态导出库:

if (OSXCROSS)
add_library(FlameSteelCore STATIC ${SOURCE_FILES})
else()

接下来,您可能会遇到这样的情况:您没有 osxcross 所需的库,我在使用 SDL2 时遇到过这种情况。 osxcross 支持现成的库包 – macports。例如,安装SDL2-mixer:

osxcross-macports -v install libsdl2_mixer

此后,您可以像平常一样在 cmake-make 链接中开始构建库/应用程序,如果需要,请不要忘记指定库的静态链接。

手动组装库

目前,我在静态链接期间遇到了库归档不正确的问题;在构建最终应用程序时,我收到错误:

file was built for archive which is not the architecture being linked (x86_64)

这张票非常相似,我们设法实现了解决方法可导致装配正确完成。让我们解压缩静态库并使用 osxcross 归档程序重新构建它:

ar x ../libFlameSteelCore.a
rm ../libFlameSteelCore.a
x86_64-apple-darwin19-ar rcs ../libFlameSteelCore.a *.o

我个人也认为问题之一是缺乏直接在 Ubuntu 上运行 macOS 应用程序的能力(至少具有某些功能),当然有一个项目 亲爱的,但支持仍然有很多不足之处。

来源

https://github.com/tpoechtrager/osxcross

在 Ubuntu MinGW CMake 下构建 Windows

在这篇文章中,我将描述在 Ubuntu 上使用 MinGW32 工具链为 Windows 构建库和应用程序的过程。
安装wine、mingw:

sudo apt-get install wine mingw-w64

此后,您就可以为 Windows 构建 C/C++ 应用程序了:

# C
i686-w64-mingw32-gcc helloWorld.c -o helloWorld32.exe      # 32-bit
x86_64-w64-mingw32-gcc helloWorld.c -o helloWorld64.exe    # 64-bit
 
# C++
i686-w64-mingw32-g++ helloWorld.cc -o helloWorld32.exe     # 32-bit
x86_64-w64-mingw32-g++ helloWorld.cc -o helloWorld64.exe   # 64-bit

可以使用wine检查收集的exe。

接下来,让我们看看对 CMake 构建、CMakeLists.txt 文件的更改,将 MinGW 特定的内容添加到构建文件中:

if (MINGW32)
set(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
set(CMAKE_RANLIB i686-w64-mingw32-ranlib)
endif()

// для сборки shared dll
elseif (MINGW32)
add_library(FlameSteelEngineGameToolkit.dll SHARED ${SOURCE_FILES})
else()

// обязательно линкуем со всеми зависимостями
if (MINGW32)
target_link_libraries(
                        FlameSteelEngineGameToolkit.dll 
                        -static-libgcc
                        -static-libstdc++
                        SDL2 
                        SDL2_mixer 
                        /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/FlameSteelCore.dll
                        /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/FlameSteelBattleHorn.dll
                        /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/FlameSteelCommonTraits.dll)

set_target_properties(FlameSteelEngineGameToolkit.dll PROPERTIES
        PREFIX ""
        SUFFIX ""
        LINK_FLAGS "-Wl,--add-stdcall-alias"
        POSITION_INDEPENDENT_CODE 0 # this is to avoid MinGW warning; 
        # MinGW generates position-independent-code for DLL by default
)
else()

收集:

cmake -DMINGW32=1 .
make

输出将是 dll 或 exe,具体取决于您要收集的内容。对于一个工作示例,您可以查看新的 Cube-Art-Project 及其库的存储库:
https://gitlab.com/demensdeum/cube-art-project
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL
https://gitlab.com/demensdeum/cube-art-project-bootstrap

来源
https://arrayfire.com/cross-compile-to-windows-from-linux/

ChromeDriver 的简单 Emscripten 自动测试

在这篇笔记中,我将描述为 Chrome 浏览器的 ChromeDriver 运行自动测试的实现,它运行使用 Emscripten 从 C++ 翻译而来的模块自动测试,读取控制台输出并返回测试结果。
首先你需要安装selenium,对于Python 3-Ubuntu,这是这样完成的:

pip3 install selenium

接下来,从官网下载ChromeDriver,例如将chromedriver放在/usr/local/bin下,然后就可以开始实现自动测试了。
下面我将给出自动测试代码,该代码启动 Chrome 浏览器,并在 Emscripten 上打开自动测试页面,检查是否存在文本“Window test successed”:

import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

capabilities = DesiredCapabilities.CHROME
capabilities['goog:loggingPrefs'] = { 'browser':'ALL' }
driver = webdriver.Chrome()
driver.get("http://localhost/windowInitializeTest/indexFullscreen.html")

time.sleep(2)

exitCode = 1

for entry in driver.get_log('browser'):
    if entry["source"] == "console-api":
        message = entry["message"]
        if "Window test succeded" in message:
            print("Test succeded")
            exitCode = 0

driver.close()
exit(exitCode)

将测试保存为 main.py 并运行 python3 main.py