从0开发3D引擎(十二):使用领域驱动设计,从最小3D程序中提炼引擎(第三部分)

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

从0开发3D引擎(十二):使用领域驱动设计,从最小3D程序中提炼引擎(第三部分)

Wonder-YYC   2020-03-05 我要评论
[TOC] 大家好,本文根据领域驱动设计的成果,实现了init API。 # 上一篇博文 [从0开发3D引擎(十一):使用领域驱动设计,从最小3D程序中提炼引擎(第二部分)](https://www.cnblogs.com/chaogex/p/12411575.html) # 下一篇博文 [从0开发3D引擎(十三):使用领域驱动设计,从最小3D程序中提炼引擎(第四部分)](https://www.cnblogs.com/chaogex/p/12418292.html#%E6%9C%AC%E6%96%87%E5%AE%8C%E6%95%B4%E4%BB%A3%E7%A0%81%E5%9C%B0%E5%9D%80) # 继续实现 ## 实现“DirectorJsAPI.init” ### 实现“保存WebGL上下文”限界上下文 1、在src/api_layer/api/中加入DirectorJsAPI.re,实现API DirectorJsAPI.re代码为: ```re let init = DirectorApService.init; ``` 2、在src/infrastructure_layer/external/external_object/中加入Error.re,负责处理“js异常”这个外部对象 Error.re代码为: ```re //根据错误信息(string类型),创建并抛出“js异常”对象 let error = msg => Js.Exn.raiseError(msg); //根据“js异常”对象,抛出它 let throwError: Js.Exn.t => unit = [%raw err => {| throw err; |}]; ``` 3、在src/application_layer/service/中加入DirectorApService.re,实现应用服务 DirectorApService.re代码为: ```re let init = contextConfigJsObj => { CanvasCanvasEntity.getCanvas() |> OptionContainerDoService.get //OptionContainerDoService.get函数返回的是Result容器的包装值,需要调用ResultContainerVO.bind函数来处理容器内部的值 |> ResultContainerVO.bind(canvas => { SetWebGLContextSetWebGLContextDoService.setGl( contextConfigJsObj, canvas, ) }) //应用服务DirectorApService负责用抛出异常的方式处理Result错误 |> ResultContainerVO.handleFail(Error.throwError); }; ``` 关于bind函数的使用,可以参考[从0开发3D引擎(五):函数式编程及其在引擎中的应用->bind](https://www.cnblogs.com/chaogex/p/12172930.html#bind) 4、修改CanvasCanvasEntity.re,实现getCanvas函数 CanvasCanvasEntity.re相关代码为: ```re let getCanvas = () => { Repo.getCanvas(); }; ``` 5、把最小3D程序的WebGL1.re放到src/infrastructure_layer/external/library/中,保留所有的代码 WebGL1.re代码为: ```re open Js.Typed_array; type webgl1Context; type program; type shader; type buffer; type attributeLocation = int; type uniformLocation; type bufferTarget = | ArrayBuffer | ElementArrayBuffer; type usage = | Static; type contextConfigJsObj = { . "alpha": bool, "depth": bool, "stencil": bool, "antialias": bool, "premultipliedAlpha": bool, "preserveDrawingBuffer": bool, }; [@bs.send] external getWebGL1Context: ('canvas, [@bs.as "webgl"] _, contextConfigJsObj) => webgl1Context = "getContext"; [@bs.send.pipe: webgl1Context] external createProgram: program = ""; [@bs.send.pipe: webgl1Context] external useProgram: program => unit = ""; [@bs.send.pipe: webgl1Context] external linkProgram: program => unit = ""; [@bs.send.pipe: webgl1Context] external shaderSource: (shader, string) => unit = ""; [@bs.send.pipe: webgl1Context] external compileShader: shader => unit = ""; [@bs.send.pipe: webgl1Context] external createShader: int => shader = ""; [@bs.get] external getVertexShader: webgl1Context => int = "VERTEX_SHADER"; [@bs.get] external getFragmentShader: webgl1Context => int = "FRAGMENT_SHADER"; [@bs.get] external getHighFloat: webgl1Context => int = "HIGH_FLOAT"; [@bs.get] external getMediumFloat: webgl1Context => int = "MEDIUM_FLOAT"; [@bs.send.pipe: webgl1Context] external getShaderParameter: (shader, int) => bool = ""; [@bs.get] external getCompileStatus: webgl1Context => int = "COMPILE_STATUS"; [@bs.get] external getLinkStatus: webgl1Context => int = "LINK_STATUS"; [@bs.send.pipe: webgl1Context] external getProgramParameter: (program, int) => bool = ""; [@bs.send.pipe: webgl1Context] external getShaderInfoLog: shader => string = ""; [@bs.send.pipe: webgl1Context] external getProgramInfoLog: program => string = ""; [@bs.send.pipe: webgl1Context] external attachShader: (program, shader) => unit = ""; [@bs.send.pipe: webgl1Context] external bindAttribLocation: (program, int, string) => unit = ""; [@bs.send.pipe: webgl1Context] external deleteShader: shader => unit = ""; [@bs.send.pipe: webgl1Context] external createBuffer: buffer = ""; [@bs.get] external getArrayBuffer: webgl1Context => bufferTarget = "ARRAY_BUFFER"; [@bs.get] external getElementArrayBuffer: webgl1Context => bufferTarget = "ELEMENT_ARRAY_BUFFER"; [@bs.send.pipe: webgl1Context] external bindBuffer: (bufferTarget, buffer) => unit = ""; [@bs.send.pipe: webgl1Context] external bufferFloat32Data: (bufferTarget, Float32Array.t, usage) => unit = "bufferData"; [@bs.send.pipe: webgl1Context] external bufferUint16Data: (bufferTarget, Uint16Array.t, usage) => unit = "bufferData"; [@bs.get] external getStaticDraw: webgl1Context => usage = "STATIC_DRAW"; [@bs.send.pipe: webgl1Context] external getAttribLocation: (program, string) => attributeLocation = ""; [@bs.send.pipe: webgl1Context] external getUniformLocation: (program, string) => Js.Null.t(uniformLocation) = ""; [@bs.send.pipe: webgl1Context] external vertexAttribPointer: (attributeLocation, int, int, bool, int, int) => unit = ""; [@bs.send.pipe: webgl1Context] external enableVertexAttribArray: attributeLocation => unit = ""; ""; [@bs.send.pipe: webgl1Context] external uniformMatrix4fv: (uniformLocation, bool, Float32Array.t) => unit = ""; [@bs.send.pipe: webgl1Context] external uniform1i: (uniformLocation, int) => unit = ""; [@bs.send.pipe: webgl1Context] external uniform3f: (uniformLocation, float, float, float) => unit = ""; [@bs.send.pipe: webgl1Context] external drawElements: (int, int, int, int) => unit = ""; [@bs.get] external getFloat: webgl1Context => int = "FLOAT"; [@bs.send.pipe: webgl1Context] external clearColor: (float, float, float, float) => unit = ""; [@bs.send.pipe: webgl1Context] external clear: int => unit = ""; [@bs.get] external getColorBufferBit: webgl1Context => int = "COLOR_BUFFER_BIT"; [@bs.get] external getDepthBufferBit: webgl1Context => int = "DEPTH_BUFFER_BIT"; [@bs.get] external getDepthTest: webgl1Context => int = "DEPTH_TEST"; [@bs.send.pipe: webgl1Context] external enable: int => unit = ""; [@bs.get] external getTriangles: webgl1Context => int = "TRIANGLES"; [@bs.get] external getUnsignedShort: webgl1Context => int = "UNSIGNED_SHORT"; [@bs.get] external getCullFace: webgl1Context => int = "CULL_FACE"; [@bs.send.pipe: webgl1Context] external cullFace: int => unit = ""; [@bs.get] external getBack: webgl1Context => int = "BACK"; ``` 6、在srchttps://img.qb5200.com/download-x/domain_layerhttps://img.qb5200.com/download-x/domain/init/set_webgl_context/service/中加入SetWebGLContextSetWebGLContextDoService.re,创建领域服务SetWebGLContext SetWebGLContextSetWebGLContextDoService.re代码为: ```re let setGl = (contextConfigJsObj, canvas): ResultContainerVO.t(unit, Js.Exn.t) => { ContextContextEntity.setGl(contextConfigJsObj, canvas) |> ResultContainerVO.succeed; }; ``` 7、修改ContextContextEntity.re,实现setGl函数 ContextContextEntity.re相关代码为: ```re let setGl = (contextConfigJsObj, canvas) => { ContextRepo.setGl(WebGL1.getWebGL1Context(canvas, contextConfigJsObj)); }; ``` 8、修改ContextPOType.re,定义Context PO的gl字段的数据类型 ContextPOType.re相关代码为: ```re type context = { gl: option(WebGL1.webgl1Context), ... }; ``` 9、修改ContextRepo.re,实现仓库对Context PO的gl字段的操作 ContextRepo.re代码为: ```re let getGl = gl => { //将Option转换为Result Repo.getContext().gl |> OptionContainerDoService.get; }; let setGl = gl => { Repo.setContext({...Repo.getContext(), gl: Some(gl)}); }; ``` 10、修改CreateRepo.re,实现创建Context PO的gl字段 CreateRepo.re相关代码为: ```re let create = () => { ... context: { gl: None, ... }, }; ``` ### 实现“初始化所有Shader”限界上下文 1、重写DirectorApService.re DirectorApService.re代码为: ```re let init = contextConfigJsObj => { CanvasCanvasEntity.getCanvas() |> ResultContainerVO.bind(canvas => { SetWebGLContextSetWebGLContextDoService.setGl( contextConfigJsObj, canvas, ) |> ResultContainerVO.bind(() => {InitShaderInitShaderDoService.init()}) }) |> ResultContainerVO.handleFail(Error.throwError); }; ``` 2、加入值对象InitShader 在[从0开发3D引擎(十):使用领域驱动设计,从最小3D程序中提炼引擎(第一部分)](https://www.cnblogs.com/chaogex/p/12408831.html)的“设计值对象InitShader”中,我们已经定义了值对象InitShader的类型,所以我们直接将设计转换为实现: 在srchttps://img.qb5200.com/download-x/domain_layerhttps://img.qb5200.com/download-x/domain/init/init_shader/value_object/中加入InitShaderInitShaderVO.re,创建值对象InitShader InitShaderInitShaderVO.re代码为: ```re type singleInitShader = { shaderId: string, vs: string, fs: string, }; type t = list(singleInitShader); ``` 3、在srchttps://img.qb5200.com/download-x/domain_layerhttps://img.qb5200.com/download-x/domain/shader/shader/value_object/中加入ProgramShaderVO.re,创建值对象Program,它的DO对应一个WebGL的program对象 ProgramShaderVO.re代码为: ```re type t = | Program(WebGL1.program); let create = program => Program(program); let value = program => switch (program) { | Program(value) => value }; ``` 4、修改聚合根ShaderManager的DO 根据识别的引擎逻辑: - 在初始化所有Shader时,创建每个Program - 在渲染每个三角形时,根据Shader名称获得关联的Program 我们需要根据Shader id获得关联的Program,所以在ShaderManager DO中应该加入一个immutable hash map,它的key为Shader id,value为值对象Program的DO。 应该在领域视图的“容器”限界上下文中,加入值对象ImmutableHashMap、值对象MutableHashMap,其中ImmutableHashMap用于实现不可变的hash map,MutableHashMap用于实现可变的hash map。 现在来具体实现它们: 1)在srchttps://img.qb5200.com/download-x/domain_layerhttps://img.qb5200.com/download-x/domain/structure/container/value_object/中创建文件夹hash_map/ 2)在hash_map/文件夹中加入ImmutableHashMapContainerVO.re、MutableHashMapContainerVO.re、HashMapContainer.re、HashMapContainerType.re ImmutableHashMapContainerVO.re负责实现Immutable Hash Map; MutableHashMapContainerVO.re负责实现Mutable Hash Map; HashMapContainer.re从两者中提出的公共代码; HashMapContainerType.re定义HashMap的类型。 因为HashMapContainer需要使用reduce来遍历数组,这个操作属于通用操作,应该作为领域服务,所以在领域视图的“容器”限界上下文中,加入领域服务Array。在srchttps://img.qb5200.com/download-x/domain_layerhttps://img.qb5200.com/download-x/domain/structure/container/service/中加入ArrayContainerDoService.re,创建领域服务Array。 相关代码如下: ArrayContainterDoService.re ```re let reduceOneParam = (func, param, arr) => { //此处为了优化,使用for循环和mutable变量来代替Array.reduce let mutableParam = ref(param); for (i in 0 to Js.Array.length(arr) - 1) { mutableParam := func(. mutableParam^, Array.unsafe_get(arr, i)); }; mutableParam^; }; ``` HashMapContainerType.re ```re type t('key, 'value) = Js.Dict.t('value); type t2('value) = t(string, 'value); ``` HashMapContainer.re ```re let createEmpty = (): HashMapContainerType.t2('a) => Js.Dict.empty(); let get = (key: string, map: HashMapContainerType.t2('a)) => Js.Dict.get(map, key); let entries = (map: HashMapContainerType.t2('a)): array((Js.Dict.key, 'a)) => map |> Js.Dict.entries; let _mutableSet = (key: string, value, map) => { Js.Dict.set(map, key, value); map; }; let _createEmpty = (): Js.Dict.t('a) => Js.Dict.empty(); let copy = (map: HashMapContainerType.t2('a)): HashMapContainerType.t2('a) => map |> entries |> ArrayContainerDoService.reduceOneParam( (. newMap, (key, value)) => newMap |> _mutableSet(key, value), _createEmpty(), ); ``` ImmutableHashMapContainerVO.re ```re type t('key, 'value) = HashMapContainerType.t('key, 'value); let createEmpty = HashMapContainer.createEmpty; let set = (key: string, value: 'a, map: HashMapContainerType.t2('a)) : HashMapContainerType.t2('a) => { let newMap = map |> HashMapContainer.copy; Js.Dict.set(newMap, key, value); newMap; }; let get = HashMapContainer.get; ``` MutableHashMap.re ```re type t('key, 'value) = HashMapContainerType.t('key, 'value); let createEmpty = HashMapContainer.createEmpty; let set = (key: string, value: 'a, map: HashMapContainerType.t2('a)) => { Js.Dict.set(map, key, value); map; }; let get = HashMapContainer.get; ``` 现在我们可以通过修改ShaderManagerShaderEntity.re来修改ShaderManager的DO,加入programMap字段 ShaderManagerShaderEntity.re相关代码为: ```re type t = { ... programMap: ImmutableHashMapContainerVO.t2(ShaderShaderEntity.t, ProgramShaderVO.t), }; ``` 5、创建领域服务BuildInitShaderData,实现构造值对象InitShader 1)在srchttps://img.qb5200.com/download-x/domain_layerhttps://img.qb5200.com/download-x/domain/init/init_shader/service/中加入BuildInitShaderDataInitShaderDoService.re,创建领域服务BuildInitShaderData BuildInitShaderDataInitShaderDoService.re代码为: ```re let build = () => { ShaderManagerShaderEntity.getAllGLSL() |> List.map(((shaderName, glsl)) => { ( { shaderId: ShaderShaderEntity.getId(shaderName), vs: GLSLShaderVO.getVS(glsl), fs: GLSLShaderVO.getFS(glsl), }: InitShaderInitShaderVO.singleInitShader ) }); }; ``` 2)修改GLSLShaderVO.re,实现getVS、getFS函数 GLSLShaderVO.re相关代码为: ```re let getVS = glsl => switch (glsl) { | GLSL(vs, fs) => vs }; let getFS = glsl => switch (glsl) { | GLSL(vs, fs) => fs }; ``` 3)修改ShaderManagerShaderEntity.re,加入getAllGLSL函数 ShaderManagerShaderEntity.re相关代码为: ```re let getAllGLSL = () => { ShaderManagerRepo.getAllGLSL(); }; ``` 4)修改ShaderManagerRepo.re,加入getAllGLSL函数 ShaderManagerShaderEntity.re相关代码为: ```re let getAllGLSL = () => { Repo.getShaderManager().glsls |> List.map(((shaderId, (vs, fs))) => { (ShaderShaderEntity.create(shaderId), GLSLShaderVO.create((vs, fs))) }); }; ``` 6、在srchttps://img.qb5200.com/download-x/domain_layerhttps://img.qb5200.com/download-x/domain/init/init_shader/service/中加入InitShaderInitShaderDoService.re,创建领域服务InitShader InitShaderInitShaderDoService.re代码为: ```re let init = (): ResultContainerVO.t(unit, Js.Exn.t) => { ContextContextEntity.getGl() |> ResultContainerVO.bind(gl => { //从着色器DO数据中构建值对象InitShader BuildInitShaderDataInitShaderDoService.build() |> ResultContainerVO.tryCatch(initShaderData => { initShaderData |> List.iter( ( {shaderId, vs, fs}: InitShaderInitShaderVO.singleInitShader, ) => { let program = ContextContextEntity.createProgram(gl); /* 注意:领域服务不应该直接依赖Repo 应该通过实体ContextContextEntity而不是ShaderManagerRepo来将program设置到ShaderManager PO的programMap中! */ ContextContextEntity.setProgram(shaderId, program); ContextContextEntity.initShader(vs, fs, program, gl) |> ignore; //用于运行测试 Js.log((shaderId, vs, fs)); }) }) }); }; ``` 7、修改ContextContextEntity.re,实现相关函数 ContextContextEntity.re相关代码为: ```re let getGl = () => { ContextRepo.getGl(); }; ... let createProgram = gl => gl |> WebGL1.createProgram; let setProgram = (shaderId, program) => { ShaderManagerRepo.setProgram(shaderId, program); }; let _compileShader = (gl, glslSource, shader) => { WebGL1.shaderSource(shader, glslSource, gl); WebGL1.compileShader(shader, gl); WebGL1.getShaderParameter(shader, WebGL1.getCompileStatus(gl), gl) === false ? { let message = WebGL1.getShaderInfoLog(shader, gl); //这里为了实现“从0开发3D引擎(十):使用领域驱动设计,从最小3D程序中提炼引擎(第一部分)”提出的“处理错误优化”,用“抛出异常”而不是Result来处理错误 Error.error( {j|shader info log: $message glsl source: $glslSource |j}, ); } : shader; }; let _linkProgram = (program, gl) => { WebGL1.linkProgram(program, gl); WebGL1.getProgramParameter(program, WebGL1.getLinkStatus(gl), gl) === false ? { let message = WebGL1.getProgramInfoLog(program, gl); //这里为了实现“从0开发3D引擎(十):使用领域驱动设计,从最小3D程序中提炼引擎(第一部分)”提出的“处理错误优化”,用“抛出异常”而不是Result来处理错误 Error.error({j|link program error: $message|j}); } : program; }; let initShader = (vsSource: string, fsSource: string, program, gl) => { let vs = _compileShader( gl, vsSource, WebGL1.createShader(WebGL1.getVertexShader(gl), gl), ); let fs = _compileShader( gl, fsSource, WebGL1.createShader(WebGL1.getFragmentShader(gl), gl), ); WebGL1.attachShader(program, vs, gl); WebGL1.attachShader(program, fs, gl); WebGL1.bindAttribLocation(program, 0, "a_position", gl); _linkProgram(program, gl); WebGL1.deleteShader(vs, gl); WebGL1.deleteShader(fs, gl); program; }; ``` 8、修改ShaderManagerPOType.re,ShaderManager PO加入programMap字段 虽然programMap也是hash map,但不能直接使用领域层的值对象ImmutableHashMapContainerVO来定义它的类型!因为PO属于基础设施层,它不能依赖领域层! 因此,我们应该在基础设施层的“数据”中创建一个ImmutableHashMap.re模块,尽管它的类型和函数都与ImmutableHashMapContainerVO一样。 在src/infrastructure_layerhttps://img.qb5200.com/download-x/data/中创建文件夹structure/,在该文件夹中加入ImmutableHashMap.re。 为了方便,目前暂时直接用ImmutableHashMapContainerVO来实现ImmutableHashMap。 ImmutableHashMap.re代码为: ```re type t2('key, 'a) = ImmutableHashMapContainerVO.t2('key, 'a); let createEmpty = ImmutableHashMapContainerVO.createEmpty; let set = ImmutableHashMapContainerVO.set; ``` 修改ShaderManagerPOType.re,ShaderManager PO加入programMap字段: ```re type shaderManager = { ... programMap: ImmutableHashMap.t2(shaderId, WebGL1.program), }; ``` 9、修改ShaderManagerRepo.re,实现setProgram函数 ShaderManagerRepo.re相关代码为: ```re let _getProgramMap = ({programMap}) => programMap; let setProgram = (shaderId, program) => { Repo.setShaderManager({ ...Repo.getShaderManager(), programMap: _getProgramMap(Repo.getShaderManager()) //这里也使用基础设施层的“数据”的ImmutableHashMap,因为操作的是ShaderManager PO的programMap |> ImmutableHashMap.set(shaderId, program), }); }; ``` 10、修改CreateRepo.re,实现创建ShaderManager PO的programMap字段 CreateRepo.re相关代码为: ```re let create = () => { ... shaderManager: { ... programMap: ImmutableHashMap.createEmpty(), }, }; ``` ### 实现用户代码并运行测试 1、在项目根目录上执行webpack命令,更新wd.js文件 ```js yarn webpack ``` 2、实现index.html相关代码 index.html代码为: ```html ``` 3、运行测试 运行index.html页面 打开控制台,可以看到打印了两次数组,每次数组内容为[Shader名称, vs, fs],其中第一次的Shader名称为“shader2”,第二次为“shader1”

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们