OPAMC架构介绍

1 OPAMC的概念

        OPAMC架构来源于ECS架构的思想,用于实现面向对象绘图,采用Racket语言(Lisp语言的一个方言)实现。

        ECS架构(全称Entity-Component-System,即实体-组件-系统)是一种软件架构模式,主要用于游戏开发。ECS遵循组合优于继承的原则,游戏内的每一个基本单元都是一个实体,每个实体又由一个或多个组件构成,每个组件仅仅包含代表其特性的数据(即在组件中没有任何方法),系统是处理拥有一个或多个相同组件的实体集合的工具,它只拥有行为(即在系统中没有任何数据)。实体与组件是一个一对多的关系,实体拥有怎样的能力,完全是取决于其拥有哪些组件。通过动态添加或删除组件,可以在(游戏)运行时改变实体的行为。换句话说,每个实体不是由“类型“定义的,而是由与其关联的组件定义的。组件如何与实体相关连,取决于所使用的ECS框架如何设计。

        ECS的规则——“组件没有函数,系统没有状态”。

        ECS架构规避了面向对象编程(OOP)中继承造成的不足——为对象强行建立父子关系,这种极度耦合产生的各种不灵活:耦合性的改动、不充分的继承、不灵活的重用、难以抉择的归类。

        OPAMC架构同样采用组合的方式组织数据,但规避了ECS架构中实体数量及组件数量为预先设定的问题,使它们可以根据用户需求进行动态增减。

        OPAMC架构使用对象(绘图对象,O)、属性(对象属性,P)、行为(对象行为,A)、管理器(M)、协调器(C)五个概念,将绘图对象的属性采用组合方式进行数据组织,即一切皆属性,实现面向数据编程。而在处理具体的行为、管理、协调时,采用面向对象(OOP)进行封装。

2 OPAMC的组成

  • O:object,对象。指绘图操作对象,包含实体及虚体。对象包括:图元、实体、组、模型。对组的内部成员(实体、组的引用)作为对象的属性;实体的内部成员(图元)作为对象属性;图元的几何描述结构作为对象属性。

  • P:property,属性。指描述对象的特征的数据结构值。属性仅提供数据(数据结构值),不包含任何行为,没有函数。可以将各种属性结构分类放在不同模块内。

  • A:action,行为。对象的行为逻辑,负责对一组(或一个)特定对象属性进行处理,实现对象属性的控制及实现对象行为操作。行为模块之间独立存在,不互相调用。

  • M:manager,管理器,实现对象、属性、行为的创建、添加、修改、查询及删除(CRUD),分别对应通过对象管理器、属性管理器、行为管理器实现。管理器模块与对象、属性、行为模块分开设置,实现代码完全解偶。

  • C:coordinator,协调器。为避免模块的交叉引用并使程序逻辑更清晰,通过协调器实现对象管理器、属性管理器、行为管理器之间的协调关系;实现对象、属性、行为的综合管理,它也是对外的接口(更上层程序通过调用协调器程序间接管理数据及行为,而不是直接调用各数据及管理器程序)。各管理器仅完成自己范围内的简单操作,所有交叉操作均在协调器内进行。

3 OPAMC模块划分

  1. 对象管理器模块:包含对象管理器。以对象管理器为基类,可根据需要扩展出图元对象管理器、实体对象管理器、组对象管理器三个模块。

  2. 属性数据结构模块:可按属性数据结构分类设置不同模块。

  3. 属性管理器模块。

  4. 属性值管理器模块。

  5. 行为模块:行为类模块,并以行为类为基类,根据不同行为需求派生各自行为类。

  6. 行为管理器模块。

  7. 协调器模块。

4 OPAMC文件树结构

        OPAMC架构整体作为一个单独文件夹。其下分为如下文件及文件夹组成文件树:

-opamc(OPAMC架构文件夹)

|-property(属性文件夹)

|+所有经分类的属性模块

|-action(行为文件夹)

|+所有行为的模块

|-manager(管理器文件夹)

|-object-manager.rkt(对象管理器模块)

|-property-manager.rkt(属性管理器模块)

|-property-value-manager.rkt(属性值管理器模块)

|-action-manager.rkt(行为管理器模块)

|-coordinator.rkt(协调器模块)

        由此可以看出,OPAMC架构由:一个架构,三个文件夹(属性文件夹、行为文件夹、管理器文件夹),四个管理器(对象管理器、属性管理器、属性值管理器、行为管理器),一个协调器等组成。

5 OPAMC架构整合

5.1 OPAMC架构数据
  • 对象(objects):(hash (对象id/o . (set 属性id/p)...)

  • 属性(properties):(hash (类型type . (hash 属性id/p . (set 对象id/o))...)...)

  • 属性值(property-values):(hash (类型type . (hash (属性id/p . 属性值)...)...)

  • 行为(actions):(hash (类型type . 行为id/a)...)

5.2 OPAMC操作
  • 对象管理器(object-manager):创建对象、销毁对象;取得对象散列表;取得属性集合;添加属性、移除属性。

  • 属性管理器(property-manager):添加属性、删除属性;取得对象集合;添加对象、移除对象。

  • 属性值管理器(property-value-manager):注册属性类型;取得属性值散列表;添加属性值、销毁属性值、取得属性值、设置属性值。

  • 行为管理器(action-manager):注册行为;取得行为散列表;取得行为;取得对象集合;添加对象、移除对象;判断行为类型是否存在。

  • 协调器(coordinator):协调manager/o、manager/p、manager/pv、manager/a四个管理器。创建对象、销毁对象、取得对象散列表;注册属性、添加属性、移除属性、取得属性、取得属性值、更新属性;注册行为、添加行为、取得行为散列表、取得行为。

        管理器的操作严格控制在简单、直接范围的操作,凡是交叉判断、交叉操作均在协调器内实现。

        数据的索引:将对象、属性值、行为均分配全局唯一id值,通过id值进行索引。由于Racket语言中结构(struct)和类(class)均为一级值,因此对于用类型(type)进行索引分类的,直接用结构类型值及类类型值作为类型(type)索引值,这样既直观又简单。

6 OPAMC实现代码

6.1 属性代码示例

        所有属性均用结构类型表示,同类属性结构放在一个模块内。

  • 属性模块示例一:

    ;transform-property.rkt
    ;变换属性。
    #lang racket
    (provide
     (struct-out transform))
    (struct transform
      (position rotation scale))
  • 属性模块示例二:

    ;geometry-property.rkt
    ;几何属性。
    #lang racket
    (provide
     (struct-out point)
     (struct-out line)
     (struct-out arc))
    ;点:
    (struct point (x0 y0))
    ;线段:
    (struct line (p0 p1))
    ;圆弧:
    (struct arc (pc p0 p1))
6.2 行为代码示例

        行为采用类来描述,以便于对不同行为的封装。行为类仅包含操作对象集合,而且该集合来自于action%基类,以确保行为操作对象均针对所有对象(如果要对对象进行分类则需要另行处理)。一般每一个行为类独占一个模块。

  • 行为基类模块示例:

    ;action.rkt
    ;行为基类。
    ;行为(action)负责对一组具有特定属性(property)
    ;的对象(object)表进行处理。
    ;这里的“特定”是指对象应该包含所有该行为需要的属性。
    #lang racket
    (provide action%)
    ;定义行为类:
    ;本类为基类,每个行为类都可以从这个基类继承,
    ;从而使得行为管理器(action-manager)可以保留指向行为的引用列表。
    (define action%
      (class object%
        (super-new)
        (field
         ;对象集合:
         [objects (mutable-set)])
        ;添加对象:
        (define/public (add-object id/o)
          (set-add! objects id/o))
        ;移除对象:
        (define/public (remove-object id/o)
          (set-remove! objects id/o))
        ;取得对象集合:
        (define/public (get-objects)
          objects)
        ))

  • 行为派生类模块示例:

    ;draw-action.rkt
    ;绘制行为。
    #lang racket
    (require "action.rkt"
             "../coordinator.rkt"
             "../property/transform-property.rkt"
             "../property/display-property.rkt")
    (provide draw-action%)
    ;定义绘制行为类:
    (define draw-action%
      (class action%
        (super-new)
        (inherit-field objects)
        (field [w 30]
               [coordinator (make-coordinator)])
        (define/public (draw dc)
          (set-for-each
           objects
           (lambda (id/o)
             (let ([p/transform
                    (send coordinator get-property-value transform
                          (send coordinator get-property id/o transform))])
               (when p/transform
                 (let ([pos (transform-position p/transform)]
                       [ro (transform-rotation p/transform)]
                       [sc (transform-scale p/transform)])
                   (send dc set-origin (send pos get-x) (send pos get-y))
                   (send dc set-scale sc sc)
                   (send dc set-rotation ro)
                   (send dc draw-rectangle
                         (- w) (- w)
                         (* 2 w) (* 2 w))
                   ))))))
        ))

6.3 管理器代码

        每个管理器分别独立设置模块。管理器模块在OPAMC架构下可以无需改变直接使用。

        包括四个模块:对象管理器模块、属性管理器模块、属性值管理器模块、行为管理器模块。

6.3.1 对象管理器模块
;object-manager.rkt
;对象管理器。
;每个对象包含属性标识集合。
#lang racket
(require "../arithmetic/guid.rkt")
(provide
 make-object-manager)
;对象散列表,每一个键值对代表一个对象。其:
;键为对象标识值;值为对象属性标识值集合。
(define objects (make-hash))
;定义对象管理器类:
(define object-manager%
  (class object%
    (super-new)
    ;创建对象:
    (define/public (create-object)
      (let ([id/o (create-guid-string)])
        (hash-set! objects id/o (mutable-set))
        id/o))
    ;销毁对象:
    (define/public (destroy-object id/o)
      (unless (hash-empty? objects)
        (hash-remove! objects id/o)))
    ;取得对象散列表:
    (define/public (get-objects)
      objects)
    ;取得属性集合:
    (define/public (properties id/o)
      (hash-ref objects id/o (lambda () #f)))
    ;添加属性:
    (define/public (add-property id/o id/p)
      (let ([ps (properties id/o)])
        (when ps
          (set-add! (properties id/o) id/p))))
    ;移除属性:
    (define/public (remove-property id/o id/p)
      (let ([ps (properties id/o)])
        (when ps
          (set-remove! (properties id/o) id/p))))
    ))
;创建对象管理器:
(define (make-object-manager)
  (new object-manager%))
6.3.2 属性管理器模块
;property-manager.rkt
;属性管理器。
;每个属性包含对象标识集合。
#lang racket
(provide make-property-manager)
;属性散列表。其中:
;键为属性标识值;值为对象标识集合。
(define properties (make-hash))
;定义属性管理器类:
(define property-manager%
  (class object%
    (super-new)
    ;添加属性:
    (define/public (add-property id/p)
      (hash-set! properties id/p (mutable-set)))
    ;删除属性:
    ;删除属性需满足该属性没有对象需要。
    (define/public (delete-property id/p)
      (when (set-empty? (objects id/p))
        (hash-remove! properties id/p)))
    ;取得对象集合:
    (define/public (objects id/p)
      (hash-ref properties id/p (lambda () #f)))
    ;添加对象:
    (define/public (add-object id/p id/o)
      (let ([os (objects id/p)])
        (when os
          (set-add! os id/o))))
    ;移除对象:
    (define/public (remove-object id/p id/o)
      (let ([os (objects id/p)])
        (when os
          (set-remove! os id/o)))
      (when (set-empty? (objects id/p))
        (delete-property id/p)))
    ))
;创建属性管理器对象:
(define (make-property-manager)
  (new property-manager%))
6.3.3 属性值管理器模块
;property-value-manager.rkt
;属性值管理器。
#lang racket
(require "../arithmetic/guid.rkt")
(provide make-property-value-manager)
;属性类型散列表,通过属性注册产生。其:
;键为属性结构类型;值为属性值散列表。
;属性值散列表,通过添加产生,其键为属性标识值;值为属性值。
(define types (make-hash))
;定义属性值管理器类:
(define property-value-manager%
  (class object%
    (super-new)
    ;注册属性类型:
    (define/public (regist-type type)
      (hash-set! types type (make-hash)))
    ;取得指定类型属性值散列表:
    (define/public (get-values type)
      (hash-ref types type (lambda () #f)))
    ;添加属性值:
    ;如果指定属性类型不存在,则注册该属性类型。
    (define/public (add-value type value)
      (unless (has-type? type)
        (regist-type type))
      (let ([values (get-values type)]
            [id/p (create-guid-string)])
        (hash-set! values id/p value)
        id/p))
    ;销毁属性值:
    (define/public (destroy-value type id/p)
      (let ([values (get-values type)])
        (unless (hash-empty? values)
          (hash-remove! values id/p))))
    ;取得属性值:
    (define/public (get-value type id/p)
      (let ([values (get-values type)])
        (if values
            (hash-ref values id/p (lambda () #f))
            #f)))
    ;设置属性值:
    (define/public (set-value type id/p value)
      (let ([values (get-values type)])
        (hash-set! values id/p value)))
    ;查找属性值:
    ;如果找到,返回属性id,否则返回#f.
    (define/public (find-value type value)
      (let ([values (get-values type)]
            [id/p #f])
        (when values
          (hash-for-each
           values
           (lambda (k v)
             (when (equal? v value)
               (set! id/p k)))))
        id/p))
    ;判断属性是否为指定属性类型?
    (define/public (is-type? type id/p)
      (hash-has-key? (get-values type) id/p))
    ;判断是否存在指定属性类型?
    (define/public (has-type? type)
      (hash-has-key? types type))
    ))
;创建属性值管理器:
(define (make-property-value-manager)
  (new property-value-manager%))
6.3.4 行为管理器模块
;action-manager.rkt
;行为管理器。负责维护已注册的行为(action)。
#lang racket
(require "../arithmetic/guid.rkt")
(provide make-action-manager)
;行为散列表。其:
;键为行为类标识;值为行为类对象值。
(define actions (make-hash))
;定义行为管理器类:
(define action-manager%
  (class object%
    (super-new)
    ;注册行为:
    (define/public (regist-action type)
      (hash-set! actions type (new type)))
    ;取得行为散列表:
    (define/public (get-actions)
      actions)
    ;取得行为:
    (define/public (get-action type)
      (hash-ref actions type (lambda () #f)))
    ;取得行为包含的对象集合:
    (define/public (get-objects type)
      (let ([a (get-action type)])
        (when a
          (send a get-objects))))
    ;添加对象:
    (define/public (add-object type id/o)
      (let ([a (get-action type)])
        (when a
          (send a add-object id/o))))
    ;移除对象:
    (define/public (remove-object id/o)
      (hash-for-each
       actions
       (lambda (k v)
         (send v remove-object id/o))))
    ;判断类型是否存在:
    (define/public (has-type? type)
      (hash-has-key? actions type))
    ))
;创建行为管理器:
(define (make-action-manager)
  (new action-manager%))
6.4 协调器代码

        协调器独立设置模块,实现对象管理器、属性管理器、属性值管理器、行为管理器的交叉通信,避免模块之间的交叉引用,进行统一管理、统一对外接口。协调器模块在OPAMC架构下可以无需改变直接使用。

;coordinator.rkt
;协调器。
;负责完成对象管理器、属性管理器、属性值管理器、行为管理器之间交叉的行为,
;避免模块之间的交叉引用。
#lang racket
(require "manager/object-manager.rkt"
         "manager/property-manager.rkt"
         "manager/property-value-manager.rkt"
         "manager/action-manager.rkt")
(provide make-coordinator)
;定义协调器类:
(define coordinator%
  (class object%
    (super-new)
    (field
     [manager/o (make-object-manager)]
     [manager/p (make-property-manager)]
     [manager/pv (make-property-value-manager)]
     [manager/a (make-action-manager)])
    ;对象相关方法:-----------------------------------------
    ;创建对象:
    (define/public (create-object)
      (send manager/o create-object))
    ;销毁对象:
    (define/public (destroy-object id/o)
      (send manager/o destroy-object id/o)
      (send manager/p remove-object id/o)
      (send manager/a remove-object id/o))
    ;取得对象散列表:
    (define/public (get-objects)
      (send manager/o get-objects))
    ;属性相关方法:-------------------------------------------
    ;注册属性:
    (define/public (regist-property type)
      (unless (send manager/pv has-type? type)
        (send manager/pv regist-property type)))
    ;添加属性:
    ;如果value在属性列表中不存在,则添加。
    (define/public (add-property id/o type value)
      (let ([id/p (send manager/pv find-value type value)])
        (unless id/p
          (set! id/p (send manager/pv add-value type value))
          (send manager/p add-property id/p))
        (send manager/o add-property id/o id/p)
        (send manager/p add-object id/p id/o)))
    ;移除属性:
    (define/public (remove-property id/o id/p)
      (send manager/o remove-property id/o id/p)
      (send manager/p remove-object id/p id/o))
    ;取得属性:
    (define/public (get-property id/o type)
      (let ([id/p #f])
        (for ([id (send manager/o properties id/o)])
          #:break (not (send manager/pv is-type? type id))
          (set! id/p id))
        id/p))
    ;取得属性值:
    (define/public (get-property-value type id/p)
      (send manager/pv get-value type id/p))
    ;更新属性:
    (define/public (update-property type id/o id/p value)
      (let ([id/v (send manager/pv find-value type value)]
            [n (set-count (send manager/p objects id/p))])
        (cond
          ;给定属性值已经存在,将该存在的属性设置给对象:
          [id/v
           (send manager/p remove-object id/p id/o)
           (send manager/p add-object id/v id/o)
           (send manager/o remove-property id/o id/p)
           (send manager/o add-property id/o id/v)]
          ;属性仅属于一个对象,仅改变属性值:
          [(= n 1)
           (send manager/pv set-value type id/p value)]
          ;属性属于多个对象,新建立属性给对象:
          [(> n 1)
           (let ([id/v (send manager/pv add-value type value)])
             (remove-property id/o id/p)
             (send manager/p register-property id/v)
             (send manager/p add-object id/v id/o)
             (send manager/o add-property id/o id/p))])))
    ;行为相关方法:--------------------------------------------
    ;注册行为:
    (define/public (regist-action type)
      (unless (send manager/a has-type? type)
        (send manager/a regist-action type)))
    ;添加行为:
    (define/public (add-action id/o type)
      (send manager/a add-object type id/o))
    ;取得行为散列表:
    (define/public (get-actions)
      (send manager/a get-actions))
    ;取得行为:
    (define/public (get-action type)
      (let ([as (get-actions)])
        (if (hash-empty? as)
            #f
            (hash-ref as type))))
    ))
;创建协调器对象:
(define (make-coordinator)
  (new coordinator%))
6.5 id标识代码

        以上的对象管理器、属性值管理器、行为管理器模块中,均出现了(require "../arithmetic/guid.rkt"),其中”arithmetic“是用来放置数学计算模块的文件夹,其中的guid.rkt是用来生成id索引标识的函数generate-guid所在的模块文件。

        值的说明的是,这里采用的为随机生成的16个字符索引标识值,实际采用的id索引标识有各种选择,比如IFC规范的GUID为是二进制长度为128位的数字标识符,UUID一般为32个字符的字符串等。

        该文件的主要内容如下:

;guid.rkt
;生成唯一标志(GUID)。
#lang racket
(require racket/random)
(provide generate-guid)
;……
;作为guid的另一个选择,通过随机产生字节码:
(define (generate-guid)
 (crypto-random-bytes 16))
 ;……

7 总结

        OPAMC架构是一个伸缩性很强的以面向数据编程的软件框架,其速度不如DCS,但扩展性是其重要特点。我把它用在我本人实现的面向对象绘图的软件中,目的是为了实现”一切皆属性“的思想,让绘图处理过程逻辑上高度统一。

        大家如对OPAMC架构有不同的意见或建议,欢迎进行评论。

以上内容用Racket语言的Scribble编辑并编译生成。

你可能感兴趣的:(架构,开发语言,racket,lisp,ECS)