Coder Social home page Coder Social logo

opensmock / molecule Goto Github PK

View Code? Open in Web Editor NEW
26.0 10.0 7.0 985 KB

Molecule is a Pharo component framework.

License: MIT License

Smalltalk 100.00%
molecule pharo component framework lightweigth corba model architecture components connexion

molecule's Introduction

License Pharo 11 CI Pharo 12 CI

Molecule

Molecule Logo

Molecule is a component oriented framework for Pharo. His Component architecture approach provides an adapted structuration to User Interface (UI) or another software application which need Component features.

Molecule provides a way to describe a software application as a component group. Components communicate by use of services, parameters and event propagation. It is a Pharo implementation of the Lightweight Corba Component Model (Lightweight CCM). Molecule supports completely transparent class augmentation into component (not necessary to add code manually), based on Traits.

Documentation

Molecule documentation is available here: Molecule Documentation Home

The documentation includes some tutorials, pattern description and examples.

How to get Molecule

You can load the latest development version of Molecule or load a specific stable release with a tag, for example 1.2.10.

Continuous integration (CI) status badges show status of compatibility for all supported Pharo versions. You can use Molecule with your Pharo version when its badge is green !

Latest version

To install the latest version of Molecule in Pharo, you just need to execute the following script:

Metacello new
   baseline: 'Molecule';
   repository: 'github://OpenSmock/Molecule:main/src';
   load.

To add in your project BaselineOf:

spec baseline: 'Molecule' with: [ spec repository: 'github://OpenSmock/Molecule:main/src' ].

Specific release

To install a release in your Pharo image you just need to adapt and execute the following script. Don't forget to adapt the x.x.x tag to your wanted release in your script, for example 1.2.11.

Metacello new
   baseline: 'Molecule';
   repository: 'github://OpenSmock/Molecule:x.x.x';
   load.

To add in your project BaselineOf:

spec baseline: 'Molecule' with: [ spec repository: 'github://OpenSmock/Molecule:x.x.x' ].

Looking for an older Pharo ?

New releases of Molecule don't support old Pharo versions (< 10), but could work. Find below some Molecule branches for old Pharo versions.

Pharo 9 and 10 - last release is 1.2.8.

Pharo 8 - last release is 1.2.7.

Pharo 6 and 7 - last release is 1.1.1.

Prerequisites

Molecule Core has no dependencies.

Package 'Molecule-Benchmarks' requires SMark (https://github.com/smarr/SMark), this package contains benchmarks for working on performances.

Molecule developer menus

Molecule tries to offer a maximum number of Pharo user interface extensions to create and exploit components.

Library menu

A Molecule Component system can be monitored and inspected from the dedicated Molecule library menu. image

This menu also includes a special section for Debug and Tools, providing access to advanced features.

Contextual menus

There are mutiple accesses to Molecule features from contextual menus.

Packages contextual menu:

image

This menu provides metrics to have statistics on the Molecule code in selected packages. image

Classes contextual menu:

image

This menu provides actions and tools depending on the selected classes. With this menu you can force to define a Component, specially if you have deactivated the Molecule dynamic update or if you have a bug when a Component contract changed.

Using Components

Start and stop method

Components can be used with the start & stop method.

To start a component:

component := MyComponentClass start.

To stop a component:

MyComponentClass stop.

Components can be identified with a name. To start a component with a specific name:

componentA := MyComponentClass start: #componentA.

To stop a component identified by a name:

MyComponentClass stop: #componentA.

Component life-cycle method

Components can be used with the life-cycle method, the two methods (start & stop, life-cycle) can be combined.

Starting a component is equivalent to:

MyComponentClass deploy.
component := MyComponentClass instantiate.
MyComponentClass activate.

With a name:

MyComponentClass deploy.
componentA := MyComponentClass instantiate: #compA.
MyComponentClass activate: #compA.

Stopping a component is equivalent to:

MyComponentClass passivate.
MyComponentClass remove.
MyComponentClass undeploy.

With a name:

MyComponentClass passivate: #compA.
MyComponentClass remove: #compA.
MyComponentClass undeploy.

Some examples

Examples are available in the package 'Molecule-Examples'. Open the Transcript before running examples, some results are showed in the Transcript window.

Clock System example

MolMyClockSystem startAlarmExample.

This system uses 4 components: a server time sends global hour to a clock. The clock sends local hour to alarms and to the final user (which could be an UI). The final user can change the parameters of the system as alarm time or set a manual time for the clock. The alarm is subscribed to the clock time, and sounds when it's time.

This system provides a global example of the use of components.

Geographical Position example

Examples are further detailed in the comment of MolGeoPosExampleLauncher.

1 - Start the demo: start a GPS equipment and a Map receiver (displaying result on Transcript)

MolGeoPosExampleLauncher start.

2 - Choose between available geographical position equipments:

Change the started component of MolGeoPosEquipmentType Type on the fly.

MolGeoPosExampleLauncher swapGPSInaccurate.
MolGeoPosExampleLauncher swapGSM.
MolGeoPosExampleLauncher swapGalileo.
MolGeoPosExampleLauncher swapWiFi.
MolGeoPosExampleLauncher swapGPS.

3 - To stop the demo:

MolGeoPosExampleLauncher stop.

To know more...

Publications related to Molecule:

Molecule: live prototyping with component-oriented programming

15 Years of Reuse Experience in Evolutionary Prototyping for the Defense Industry

Reuse in component-based prototyping: an industrial experience report from 15 years of reuse

Credits

License

This project is licensed under the MIT License - see the LICENSE file for details.

molecule's People

Contributors

elepors avatar eliott-guevel avatar labordep avatar landaisb avatar lisadoyen avatar nolwennfournier avatar nyan11 avatar plantec avatar samuel29590 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

molecule's Issues

Cannot unsubscribe an originator to a receiver

When an originator remove his dependents for a component (on event) this remove cannot works.

Connection code :

MolEventSubscriber>>connectOriginator: componentName to: aComponent
	"Connect a component to event pipeline"

	self events allSelectors do: [ :event | | originator |
		
		originator := self originatorsLinks at: componentName.
		originator ifNil: [ ^ self error: 'Component originator is nil' ].
		originator when: event send: event to: aComponent.
	].

Deconnection code (not working):

MolEventSubscriber>>disconnectOriginator: componentName from: aComponent
	"private method, do not use directly"
	
	self events allSelectors do: [ :event | | originator |

		originator := self originatorsLinks at: componentName.
		originator ifNil: [ ^ self error: 'Component originator is nil' ].
		originator removeActionsWithReceiver: aComponent forEvent: event.
	]

Bug : Cannot detect correctly if components are running

Bug - Code proposition :

MolComponentManager class

isRunningComponents
	<script:'self isRunningComponents inspect'>

	Default ifNotNil:[ | components |
		components := Default homeServices deployedComponents.
		components ifNil:[ ^false ].
		components valuesDo: [ :aDictionary | aDictionary notEmpty ifTrue:[ ^true ] ].
	].
	^false

Centralize the default component name into a utility class

MolUtils class>>startComponent: aComponentClass
	"deploy, initialize and activate quickly a component and return the instance"
	
	^ self startComponent: aComponentClass named: #default

#default should be in a class enum to be used by anothers.
For example :
MolUtils class>>defaultComponentName

Bug when remove an event producer named #default

testSubscribeAddAndRemoveProducerAndUnsubscribe
	
	| component |
	component := MolUtils startComponent: MolCompleteComponentImpl.
	component getMolUsedEventsSubscriber subscribe: self.
	
	component forEvents: MolUsedEvents addProducer: #default.
	component forEvents: MolUsedEvents removeProducer: #default. 
	
	component getMolUsedEventsSubscriber unsubscribe: self. "PLA => Bug here"

Provided Events should be received after subscription enabling

This problem appear when a component which is already subscribing to an existing Events edit these providers list.

Test code that should be work (actually not) :

listenergetMyEventsSubscriber subscribe: listener.
listener forEvents: MyEvents useProducer: #newProvider. => not receiving, this is a bug

Bug : undeploy component also remove existing components intances incorrectly

Undeploy component class remove existing components without apply remove life cycle :

removeDeployedComponent: aComponentClass
	deployedComponents isEmpty ifTrue: [^self].
	deployedComponents removeKey: aComponentClass ifAbsent:[^self] "This method remove all instance without apply the correct life cycle !"

Molecule-Test Failed

3 tests not work:

  • MolMySocketTest
    Missing class: MolMySocketComponentImpl, MolMyClientComponentImpl

  • MolMyTableTest
    Missing class: MolMyTableComponentImpl, MolMyUserComponentImpl

  • MolMyWarningSystemTest
    Missing class: MolMyWarnerComponentImpl, MolMyListenerComponentImpl

enhancement - Better world menu

I found two ways to improve the world menu for molecule.

Put molecule in the correct submenu

Maybe we should put molecule wordmenu in the Library worldmenu.
Roassal3 is present in the Library menu and not directly on the main MenuBar of the pharo world.

The description for Library is : "Third party tools and applications".
(The menu for Library is in package Morphic-Core > WorldState class side > LibraryWordMenuOn).

If we put the molecule menu under Library, we should add also an icon to it (for example the MolIcon).

worldmenu-roassal
[Roassal3 under Library]

better method naming

The current world menu for molecule doesn't seem to follow any clear structure in its methods names (inside the browser).

The world menu of molecule is present in 2 packages :

  • Molecule-IDE >>> MolWorld
  • Molecule-IDE-Incubators >>> MolWorldActionsMenu

The menu and the differents submenus methods are not explicit about their content and/or their position in the main menu. By making the methods names more explicits, we could edit the menu more rapidly in the future.

The Roassal3 world menu use a different convention to labels the methods : menu{position}{title/description}On.

I suggest we could rename the menus of molecule by :

in Molecule-IDE >>> MolWorld

  • menuCommandOn -> menu00moleculeOn
  • currentComponentSystem -> menu01currentComponentSystemOn
  • menuDebugOn -> menu02debugOn
  • componentManagerCleanUp -> menu04componentManagerCleanUpOn
  • menuExamplesOn -> menu05examplesOn
  • menuGithubOn -> menu06githubOn
  • menuReportBugOn -> menu07reportBugOn
    .
  • defineComponentOn -> debugMenu01defineComponentOn
  • toggleMolLogsOn -> debugMenu02toggleMolLogsOn
  • toggleDynamicContractUpdate -> debugMenu03toggleDynamicContractUpdate
  • inspectAllComponentInstances -> debugMenu04inspectAllComponentInstances
  • etc ...

in Molecule-IDE-Incubators >>> MolWorldActionsMenu

  • menuActionsOn -> menu03ActionsOn (or menu03IncubatorToolsOn)
  • menuInspectOn -> actionsMenu01inspectOn (+ Classify method as "menu - actions")
  • menuSearchOn -> actionsMenu02searchOn (+ Classify method as "menu - actions")
  • menuCreateOn -> actionsMenu03createOn (+ Classify method as "menu - actions")

browser-molecule
[Methods names of molecule are less explicit than Roassal3]
browser-roassal

Bug: different types can be used for the name of a component when creating or calling it

The normal way to name a component is to use a symbol. So when creating it we use a symbol and when we call it elsewhere we also use a symbol (see the following screenshot).

normal

The first bug is when creating a component, we can create it using a string and it works (see the following screenshot).

strange

The second bug is when we creating a component using a string and also using a string to call it. It doesn't work and the component isn't found (see the following screenshots).

string
string bug

Putting an empty variable in a component's contract should fail

In Pharo 11, putting an instance variable into one of the parts that make a component's contract (i.e. consumed/producedComponentEvents, used/providedComponentServices) for a given Type automatically creates a get...Notifier method (as expected) in its implementations, but in this case leads to the creation of a getnilNotifier method. This getnilNotifier searches for notifiers at: nil, which isn't a trait that uses the MolComponentEvents trait.

Instead of this current behavior, the compilator should return an error when putting an empty instance variable into one or multiple parts of a component's contract.

[IDE] bug - world menu - "No Components are running"

There is 2 bugs on molecule world menu.

When you start a new molecule component, the world menu doesn't update and still display "No Components are running".

molecule-bug-no-run
[molecule components is running but the world menu say other wise]

To reproduce

  • Run MolGPSExampleLauncher start. in the playground.
  • Then just click on molecule menu (menubar of current world). It should display "No Components are running".

It's also possible to trigger another similar bug.

  • Run MolGPSExampleLauncher start. in the playground.
  • Run MenubarMorph reset. in the playground. It should have corrected the bug.
  • Run MolGPSExampleLauncher stop. in the playground.
  • Then just click on molecule menu (menubar of current world). It should display "System is running Components".

Fix

It can be fixed by triggering the MenubarMorph reset..
bug-molecule-trigger-reset

To fix this issue MenubarMorph reset. should be trigger every time the number of components running passes from 0 to any > 0, and when the number of components go to any > 0 to 0.

It can be fix in the Molecule Package.
In MolHomeServices >>> activateComponent:named: at the end of the method, if we add the MenubarMorph reset.
In MolHomeServices >>> removeComponent:named: at the end of the method, if we add the MenubarMorph reset.

But, this solution will make MenubarMorph reset. for every component added or removed even when the number go from 5 to 6 for example. It should only trigger when the number of components change from 0 to any or any to 0.

Instanciate isn't the correct form

Instanciate is the french form of the verb, instantiate being the english form of the same verb.
Same goes for instanciation that must be changed to instantation.
Source,
it's also worth mentioning that no result comes up while searching for "instanciate" in a web navigator

Tests : Subclass a of Component with another used Type

Write a test for set multiple type to a Component, and sub components which adding some others types :

  • ComponentA with TypeA and TypeB
  • Component B subclasse of ComponentA with Type C

=> Not possible because a component class should have only one type

Exception on building contextual menu (right click)

UseCase: On System browser, select a package, do not select any class, and perform a right click to open contextual menu.

Exception on MolCmdCommand - canBeExecutedInContext:

Array(Object)>>errorSubscriptBounds:
Array(Object)>>at:
MolDefineComponentCmdCommand class(MolCmdCommand class)>>canBeExecutedInContext:
ClyFullBrowserClassContext(CmdToolContext)>>allowsExecutionOf:
CmdContextMenuActivation(CmdCommandActivationStrategy)>>isActiveInContext:
CmdCommandActivator>>canExecuteCommand
CmdCommandActivator>>buildContextMenu:
CmdCommandMenuItem>>buildContextMenu:
[ :each | each buildContextMenu: submenu ] in MolCmdMenuGroup(CmdMenuGroup)>>buildContextSubMenuIn: in Block: [ :each | each buildContextMenu: submenu ]
SortedCollection(OrderedCollection)>>do:
MolCmdMenuGroup(CmdMenuGroup)>>buildContextSubMenuIn:
MolCmdMenuGroup(CmdMenuGroup)>>buildContextMenu:
MolCmdMenuGroup>>buildContextMenu:
[ :each | each buildContextMenu: aMenu ] in CmdRootMenuGroup(CmdMenuGroup)>>inlineContextMenuItemsInto: in Block: [ :each | each buildContextMenu: aMenu ]
SortedCollection(OrderedCollection)>>do:
CmdRootMenuGroup(CmdMenuGroup)>>inlineContextMenuItemsInto:
CmdRootMenuGroup(CmdMenuGroup)>>buildContextMenu:
CmdMenu>>buildContextMenuFor:
CmdContextMenuActivation class(CmdMenuCommandActivationStrategy class)>>buildContextMenuFor:inContext:
ClyQueryViewMorph>>menuColumn:row:
ClyExpandedDataSource(ClyDataSource)>>menuColumn:row:
FTTableMorph>>showMenuForIndex:
FTTableMorph>>showMenuForPosition:
FTTableMorph>>click:
MouseClickState>>click
MouseClickState>>handleEvent:from:
HandMorph>>handleEvent:
[
(morphicWorld activeHand isNotNil and: [ anEvent hand isNotNil ]) ifTrue: [
morphicWorld activeHand handleEvent: anEvent
]
] in OSWindowMorphicEventHandler>>dispatchMorphicEvent: in Block: [...
WorldState>>runStepMethodsIn:
WorldMorph>>runStepMethods

How to use multiple services and events providers ?

The problem appears when we are using multiple Components with the same contract definition (static), during the execution we are subscribe to multiple components and we are interesseted of uses same services contract from mutiple Component with different name.

For events, actually we are receiving all events but without the way to know the name of the emitter. A solution is to add the name of the emitter directly in the arguments of the events but this is not compatible with Component principles (do not modify used contract). For services, a solution is to create a services method manually which invoke the services from the targeted Component name, but this is uggly.

May be some solutions :

  • Services : Return a proxy when a service is called self myUsedComponentServices componentNamed: #componentB myServices
  • Events : get the name of the provider from the stack, as an example using thisContext to get the name of the emitter

removing a producer on component also removes eventTrait

When you want to remove a producer on your component, you can use MolAbstractComponentImpl>>forEvents:removeProducer:

forEvents: anEventTrait removeProducer: aProducer

	| producers |
	producers := self eventsSubscribers at: anEventTrait.
	producers isArray 
		ifTrue: [ (self eventsSubscribers at: anEventTrait) remove: aProducer ]
		ifFalse: [ (self eventsSubscribers removeKey: anEventTrait ) ]

However, if you defined a single producer for an eventTrait (if it's not a list of producers), this method will remove the eventTrait key. Which will result in the component forgetting it consumes this kind of events although we just wanted it to stop using some producer.

Reproduce :
Here's an example with a component myComponent consuming SomeEvents events

myComponent getSomeEventsSubscriber. "works"
myComponent forEvents: SomeEvents useProducer: #someProducer.
myComponent forEvents: SomeEvents removeProducer: #someProducer.
myComponent getSomeEventsSubscriber. "bug"

Temporary solution :
replacing this portion of the code :
ifFalse: [ (self eventsSubscribers removeKey: anEventTrait ) ]
with :

ifFalse: [ producers = aProducer
	ifTrue: [ self eventsSubscribers at: anEventTrait put: #default ] ]

will check if the producer for the given eventTrait is the one we want to remove and if it's the case, it will set the value to the default one

Bug : AlreadyPassivate and ComponentNotFound errors are raised during a ComponentManager>>cleanUp

Because of components are cleanup in a random order, component not found exceptions appears. Ignore theres errors during a cleanUp.

Code proposition for ComponentManager class :

cleanUp
	<script: 'self cleanUp'>
	| notifiers subscribers |

	"CleanUp and initialize the ComponentFactory"
	MolComponentFactory cleanUp.
	MolComponentFactory initialize.
	
	Default ifNil:[^self].
	Default locatorServices eventsSubscribers: nil.

	notifiers := MolEventNotifier allInstances.
	subscribers := MolEventSubscriber allInstances.

	"Ignore some Error during the cleanUp because the clean process can be different of the start order"
	[Default release] on: ComponentAlreadyPassivatedError, ComponentNotFoundError do:[ :e | "do nothing" ].

	Default := nil.
	notifiers do: [:n | n release].
	subscribers do: [:n | n release].

Feature : Starting a Component Cluster

Starting automatically a Component Cluster composed by a set a Components Classes linked by a componentName.
For example :

MolUtils startComponentsCluster: aComponentList named: aComponentName.

This method start each components classes using "aComponentName" instead of #default key and map automatticaly each of them inside the cluster.

Cannot add (but remove) some events producers

The point is to add some events producers like that :

MolComponentImpl>>forEvents: TMyEvents addProducer: #myProducer

And also to remove it with :

MolComponentImpl>>forEvents: anEventsTrait removeProducer: aComponentName => this method already exists

This is offer the capacity to dynamically change producers.

#SearchFacade is missing when opening define a component menu

This class is missing :

MolWorld class>>openDefineComponentDialog

<script>
| searchClass list retValue |
list := SystemNavigation default allClasses select: [ :c | 
	        c isTrait not and: [ 
		        (c allSuperclasses includes: Object) and: [ c isComponentClass ] ] ].
searchClass := **SearchFacade** classSearchIn: list.
searchClass title: 'Select the Molecule Component to define'.
retValue := searchClass openModal.
^ retValue answer

Config :

  • Windows 10
  • Pharo-11.0.0+build.459

Bug : ComponentConnector is not deleted when a Component is removed

This problem appear when I am using an old component instance as a classic class.

  • Start component
  • Stop component
  • User component API which call self componentConnector (for exemple get an EventsNotifier).

Code proposition :
MolComponentImpl>>

"component life cycle"
componentFlush

	self componentConnector release.
	self componentConnector: nil.

Remove forked component update when system annoucement are triggered

Molecule listening System Annoucement modification for Classes and Traits to update automatically components contracts.
The problem is theses updates should be forked because of when a Traits is added, updated or removed, the Traits changed notifications is triggerred before the Traits methods (and/or slots) are applied to the Class. So when Molecule check the contract content, regarding methods add/update/remove, the class changes are not here.

Class : MolComponentFactory>>initializeSystemAnnouncements and MolComponentFactory>>componentChanged: and MolComponentFactory>>contractChanged:

Bug : Some component instances are still existing in the image after a cleanUp.

When you start a system with Molecule and then you stop this system, there are some times some component instances that are still existing in the image.
They are visible by looking in the Inspect all component instances menu of Molecule after an execution of a Molecule system.

issues

To reproduce

Lunch the GPS example, wait a little, and stop the example.

MolGPSExampleLauncher start.
"wait a little"
MolGPSExampleLauncher stop.

Then go on the Inspect all component instances menu of Molecule.
And here are the lost instances.

Fix

A solution to solve this problem is to do a garbage before opening this menu.

Smalltalk garbageCollect.

By doing that lost instances are remove from the Pharo image, and so they aren't visible from this menu. Furthermore, doing a garbage don't influence instances that are alive or using by a system, so this solution don't influence the menu's function.

Contract method not generated in Pharo 7

Since Pharo 7.0, contract methods are not automaticaly generated anymore. Probably a problem with Traits in Pharo 7.0 with Pragma.

Temporary solution :
Use MolComponentFactory defineComponent: YourComponentImpl for generate your contract methods from required Traits.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.